├── .gitignore ├── Demo ├── App.xaml ├── App.xaml.cs ├── Demo.csproj ├── Images │ ├── MultiSelectTreeView.ico │ ├── select_all_11.png │ ├── select_none_11.png │ ├── select_some_11.png │ └── select_toggle_11.png ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── TypeTemplateSelector.cs └── ViewModel │ ├── ColorItemViewModel.cs │ └── TreeItemViewModel.cs ├── LICENSE ├── MultiSelectTreeView.Test ├── BindingTest.cs ├── ExpandCollapseTests.cs ├── Helper │ ├── AutomationElementHelper.cs │ ├── Keyboard.cs │ ├── Mouse.cs │ ├── NativeStructs.cs │ └── TreeApplication.cs ├── KeyboardReactionTest.cs ├── Model │ ├── BindingTree.cs │ ├── Element.cs │ ├── SimpleSampleTree.cs │ └── Tree.cs ├── MouseReactionTest.cs ├── MultiSelectTreeView.Test.csproj ├── Properties │ └── AssemblyInfo.cs └── SelectionTests.cs ├── MultiSelectTreeView.sln ├── MultiSelectTreeView.vsmdi ├── MultiSelectTreeView ├── Automation │ └── Peers │ │ ├── MultiSelectTreeViewAutomationPeer.cs │ │ ├── MultiSelectTreeViewItemAutomationPeer.cs │ │ └── MultiSelectTreeViewItemDataAutomationPeer.cs ├── Controls │ ├── BorderSelectionLogic.cs │ ├── EditTextBox.cs │ ├── FocusHelper.cs │ ├── ISelectionStrategy.cs │ ├── ListExtensions.cs │ ├── MultiSelectTreeView.cs │ ├── MultiSelectTreeViewItem.cs │ ├── SelectionMultiple.cs │ ├── SelectionSingle.cs │ └── ThicknessLeftConverter.cs ├── MultiSelectTreeView.csproj ├── MultiSelectTreeView.ico ├── Nuget │ └── MultiSelectTreeView.nuspec ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Themes │ ├── Aero.NormalColor.xaml │ ├── Aero2.NormalColor.xaml │ ├── Classic.xaml │ ├── EditTextBox.xaml │ ├── Generic.xaml │ ├── Luna.Homestead.xaml │ ├── Luna.Metallic.xaml │ ├── Luna.NormalColor.xaml │ ├── MultiSelectTreeView.Aero.xaml │ ├── MultiSelectTreeView.Aero2.xaml │ ├── MultiSelectTreeView.Classic.xaml │ ├── MultiSelectTreeViewItem.Aero.xaml │ ├── MultiSelectTreeViewItem.Aero2.xaml │ ├── MultiSelectTreeViewItem.Classic.xaml │ ├── MultiSelectTreeViewItem.Luna.xaml │ └── Royale.NormalColor.xaml └── tools │ └── NuGet.exe ├── Pictures └── W7StyleSample.jpg ├── README.md └── TraceAndTestImpact.testsettings /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | Thumbs.db 3 | *.obj 4 | *.exe 5 | *.pdb 6 | *.user 7 | *.aps 8 | *.pch 9 | *.vspscc 10 | *_i.c 11 | *_p.c 12 | *.ncb 13 | *.suo 14 | *.sln.docstates 15 | *.tlb 16 | *.tlh 17 | *.bak 18 | *.cache 19 | *.ilk 20 | *.log 21 | [Bb]in 22 | [Dd]ebug*/ 23 | *.lib 24 | *.sbr 25 | obj/ 26 | [Rr]elease*/ 27 | _ReSharper*/ 28 | [Tt]est[Rr]esult* 29 | .local/ 30 | MultiSelectTreeView/Nuget/lib/* 31 | 32 | !tools/* 33 | !MultiSelectTreeView/tools/* 34 | -------------------------------------------------------------------------------- /Demo/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Demo/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Windows; 7 | 8 | namespace Demo 9 | { 10 | /// 11 | /// Interaktionslogik für "App.xaml" 12 | /// 13 | public partial class App : Application 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Demo/Demo.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {D4239AF3-0F7C-4CAB-A70E-98541D120860} 9 | WinExe 10 | Properties 11 | Demo 12 | Demo 13 | v4.0 14 | Client 15 | 512 16 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 4 18 | 19 | 20 | Images\MultiSelectTreeView.ico 21 | 22 | 23 | true 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | full 27 | AnyCPU 28 | prompt 29 | true 30 | true 31 | false 32 | 33 | 34 | bin\Release\ 35 | TRACE 36 | true 37 | pdbonly 38 | AnyCPU 39 | prompt 40 | true 41 | true 42 | false 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 4.0 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | MSBuild:Compile 62 | Designer 63 | 64 | 65 | MSBuild:Compile 66 | Designer 67 | 68 | 69 | App.xaml 70 | Code 71 | 72 | 73 | MainWindow.xaml 74 | Code 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Code 83 | 84 | 85 | True 86 | True 87 | Resources.resx 88 | 89 | 90 | True 91 | Settings.settings 92 | True 93 | 94 | 95 | ResXFileCodeGenerator 96 | Resources.Designer.cs 97 | 98 | 99 | SettingsSingleFileGenerator 100 | Settings.Designer.cs 101 | 102 | 103 | 104 | 105 | 106 | {2854814F-EA3C-41D4-AA94-460C4694F430} 107 | MultiSelectTreeView 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 133 | -------------------------------------------------------------------------------- /Demo/Images/MultiSelectTreeView.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygoe/MultiSelectTreeView/8dadce5dfa1fce0216c906921f0edf9551dbc457/Demo/Images/MultiSelectTreeView.ico -------------------------------------------------------------------------------- /Demo/Images/select_all_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygoe/MultiSelectTreeView/8dadce5dfa1fce0216c906921f0edf9551dbc457/Demo/Images/select_all_11.png -------------------------------------------------------------------------------- /Demo/Images/select_none_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygoe/MultiSelectTreeView/8dadce5dfa1fce0216c906921f0edf9551dbc457/Demo/Images/select_none_11.png -------------------------------------------------------------------------------- /Demo/Images/select_some_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygoe/MultiSelectTreeView/8dadce5dfa1fce0216c906921f0edf9551dbc457/Demo/Images/select_some_11.png -------------------------------------------------------------------------------- /Demo/Images/select_toggle_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygoe/MultiSelectTreeView/8dadce5dfa1fce0216c906921f0edf9551dbc457/Demo/Images/select_toggle_11.png -------------------------------------------------------------------------------- /Demo/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Data; 8 | using System.Windows.Documents; 9 | using System.Windows.Input; 10 | using System.Windows.Media; 11 | using System.Windows.Media.Imaging; 12 | using System.Windows.Navigation; 13 | using System.Windows.Shapes; 14 | using Demo.ViewModel; 15 | 16 | namespace Demo 17 | { 18 | public partial class MainWindow : Window 19 | { 20 | public MainWindow() 21 | { 22 | InitializeComponent(); 23 | ShowSecondCheck_Checked(null, null); 24 | 25 | // Create some example nodes to play with 26 | var rootNode = new TreeItemViewModel(null, false) { DisplayName = "rootNode" }; 27 | var node1 = new TreeItemViewModel(rootNode, false) { DisplayName = "element1 (editable)", IsEditable = true }; 28 | var node2 = new TreeItemViewModel(rootNode, false) { DisplayName = "element2" }; 29 | var node11 = new TreeItemViewModel(node1, false) { DisplayName = "element11", Remarks = "Look at me!" }; 30 | var node12 = new TreeItemViewModel(node1, false) { DisplayName = "element12 (disabled)", IsEnabled = false }; 31 | var node13 = new TreeItemViewModel(node1, false) { DisplayName = "element13" }; 32 | var node131 = new TreeItemViewModel(node13, false) { DisplayName = "element131" }; 33 | var node132 = new TreeItemViewModel(node13, false) { DisplayName = "element132" }; 34 | var node14 = new TreeItemViewModel(node1, false) { DisplayName = "element14 with colours" }; 35 | var colorNode1 = new ColorItemViewModel(node14, false) { Color = Colors.Aqua, IsEditable = true }; 36 | var colorNode2 = new ColorItemViewModel(node14, false) { Color = Colors.ForestGreen }; 37 | var colorNode3 = new ColorItemViewModel(node14, false) { Color = Colors.LightSalmon }; 38 | var node15 = new TreeItemViewModel(node1, true) { DisplayName = "element15 (lazy loading)" }; 39 | 40 | // Add them all to each other 41 | rootNode.Children.Add(node1); 42 | rootNode.Children.Add(node2); 43 | node1.Children.Add(node11); 44 | node1.Children.Add(node12); 45 | node1.Children.Add(node13); 46 | node13.Children.Add(node131); 47 | node13.Children.Add(node132); 48 | node1.Children.Add(node14); 49 | node14.Children.Add(colorNode1); 50 | node14.Children.Add(colorNode2); 51 | node14.Children.Add(colorNode3); 52 | node1.Children.Add(node15); 53 | 54 | // Use the root node as the window's DataContext to allow data binding. The TreeView 55 | // will use the Children property of the DataContext as list of root tree items. This 56 | // property happens to be the same as each item DataTemplate uses to find its subitems. 57 | DataContext = rootNode; 58 | 59 | // Preset some node states 60 | node1.IsSelected = true; 61 | node13.IsSelected = true; 62 | node14.IsExpanded = true; 63 | } 64 | 65 | private void TheTreeView_PreviewSelectionChanged(object sender, PreviewSelectionChangedEventArgs e) 66 | { 67 | if (LockSelectionCheck.IsChecked == true) 68 | { 69 | // The current selection is locked by user request (Lock CheckBox is checked). 70 | // Don't allow any changes to the selection at all. 71 | e.CancelThis = true; 72 | } 73 | else 74 | { 75 | // Selection is not locked, apply other conditions. 76 | // Require all selected items to be of the same type. If an item of another data 77 | // type is already selected, don't include this new item in the selection. 78 | if (e.Selecting && TheTreeView.SelectedItems.Count > 0) 79 | { 80 | e.CancelThis = e.Item.GetType() != TheTreeView.SelectedItems[0].GetType(); 81 | } 82 | } 83 | 84 | //if (e.Selecting) 85 | //{ 86 | // System.Diagnostics.Debug.WriteLine("Preview: Selecting " + e.Item + (e.Cancel ? " - cancelled" : "")); 87 | //} 88 | //else 89 | //{ 90 | // System.Diagnostics.Debug.WriteLine("Preview: Deselecting " + e.Item + (e.Cancel ? " - cancelled" : "")); 91 | //} 92 | } 93 | 94 | private void ClearChildrenButton_Click(object sender, System.Windows.RoutedEventArgs e) 95 | { 96 | var selection = new object[TheTreeView.SelectedItems.Count]; 97 | TheTreeView.SelectedItems.CopyTo(selection, 0); 98 | foreach (TreeItemViewModel node in selection) 99 | { 100 | if (node.Children != null) 101 | { 102 | node.Children.Clear(); 103 | } 104 | } 105 | } 106 | 107 | private void AddChildButton_Click(object sender, System.Windows.RoutedEventArgs e) 108 | { 109 | foreach (TreeItemViewModel node in TheTreeView.SelectedItems) 110 | { 111 | if (!node.HasDummyChild) 112 | { 113 | node.Children.Add(new TreeItemViewModel(node, false) { DisplayName = "newborn child" }); 114 | node.IsExpanded = true; 115 | } 116 | } 117 | } 118 | 119 | private void ExpandNodesButton_Click(object sender, System.Windows.RoutedEventArgs e) 120 | { 121 | foreach (TreeItemViewModel node in TheTreeView.SelectedItems) 122 | { 123 | node.IsExpanded = true; 124 | } 125 | } 126 | 127 | private void HideNodesButton_Click(object sender, System.Windows.RoutedEventArgs e) 128 | { 129 | foreach (TreeItemViewModel node in TheTreeView.SelectedItems.OfType().ToArray()) 130 | { 131 | node.IsVisible = false; 132 | } 133 | } 134 | 135 | private void ShowNodesButton_Click(object sender, System.Windows.RoutedEventArgs e) 136 | { 137 | foreach (TreeItemViewModel node in TheTreeView.Items) 138 | { 139 | DoShowAll(node, (n) => true); 140 | } 141 | } 142 | 143 | private void DoShowAll(TreeItemViewModel node, Func selector) 144 | { 145 | node.IsVisible = selector(node); 146 | if (node.Children != null) 147 | { 148 | foreach (var child in node.Children) 149 | { 150 | DoShowAll(child, selector); 151 | } 152 | } 153 | } 154 | 155 | private void SelectNoneButton_Click(object sender, System.Windows.RoutedEventArgs e) 156 | { 157 | foreach (TreeItemViewModel node in TheTreeView.Items) 158 | { 159 | DoSelectAll(node, (n) => false); 160 | } 161 | } 162 | 163 | private void SelectSomeButton_Click(object sender, System.Windows.RoutedEventArgs e) 164 | { 165 | Random rnd = new Random(); 166 | foreach (TreeItemViewModel node in TheTreeView.Items) 167 | { 168 | DoSelectAll(node, (n) => rnd.Next(0, 2) > 0); 169 | } 170 | } 171 | 172 | private void SelectAllButton_Click(object sender, System.Windows.RoutedEventArgs e) 173 | { 174 | foreach (TreeItemViewModel node in TheTreeView.Items) 175 | { 176 | DoSelectAll(node, (n) => true); 177 | } 178 | } 179 | 180 | private void ToggleSelectButton_Click(object sender, System.Windows.RoutedEventArgs e) 181 | { 182 | foreach (TreeItemViewModel node in TheTreeView.Items) 183 | { 184 | DoSelectAll(node, (n) => !n.IsSelected); 185 | } 186 | } 187 | 188 | private void DoSelectAll(TreeItemViewModel node, Func selector) 189 | { 190 | node.IsSelected = selector(node); 191 | if (node.Children != null) 192 | { 193 | foreach (var child in node.Children) 194 | { 195 | DoSelectAll(child, selector); 196 | } 197 | } 198 | } 199 | 200 | private void ExpandMenuItem_Click(object sender, RoutedEventArgs e) 201 | { 202 | foreach (TreeItemViewModel node in TheTreeView.SelectedItems) 203 | { 204 | node.IsExpanded = true; 205 | } 206 | } 207 | 208 | private void RenameMenuItem_Click(object sender, RoutedEventArgs e) 209 | { 210 | foreach (TreeItemViewModel node in TheTreeView.SelectedItems) 211 | { 212 | node.IsEditing = true; 213 | break; 214 | } 215 | } 216 | 217 | private void DeleteMenuItem_Click(object sender, RoutedEventArgs e) 218 | { 219 | foreach (TreeItemViewModel node in TheTreeView.SelectedItems.Cast().ToArray()) 220 | { 221 | node.Parent.Children.Remove(node); 222 | } 223 | } 224 | 225 | private void ShowSecondCheck_Checked(object sender, RoutedEventArgs e) 226 | { 227 | if (ShowSecondCheck.IsChecked == true) 228 | { 229 | if (LastColumn.ActualWidth == 0) 230 | Width += FirstColumn.ActualWidth; 231 | LastColumn.Width = new GridLength(1, GridUnitType.Star); 232 | } 233 | else 234 | { 235 | if (LastColumn.ActualWidth > 0) 236 | Width -= LastColumn.ActualWidth; 237 | LastColumn.Width = new GridLength(0, GridUnitType.Pixel); 238 | } 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /Demo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // Allgemeine Informationen über eine Assembly werden über die folgenden 8 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 9 | // die mit einer Assembly verknüpft sind. 10 | [assembly: AssemblyTitle("Demo")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("Demo")] 15 | [assembly: AssemblyCopyright("Copyright © 2012")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar 20 | // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von 21 | // COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. 22 | [assembly: ComVisible(false)] 23 | 24 | //Um mit dem Erstellen lokalisierbarer Anwendungen zu beginnen, legen Sie 25 | //ImCodeVerwendeteKultur in der .csproj-Datei 26 | //in einer fest. Wenn Sie in den Quelldateien beispielsweise Deutsch 27 | //(Deutschland) verwenden, legen Sie auf \"de-DE\" fest. Heben Sie dann die Auskommentierung 28 | //des nachstehenden NeutralResourceLanguage-Attributs auf. Aktualisieren Sie "en-US" in der nachstehenden Zeile, 29 | //sodass es mit der UICulture-Einstellung in der Projektdatei übereinstimmt. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //Speicherort der designspezifischen Ressourcenwörterbücher 36 | //(wird verwendet, wenn eine Ressource auf der Seite 37 | // oder in den Anwendungsressourcen-Wörterbüchern nicht gefunden werden kann.) 38 | ResourceDictionaryLocation.SourceAssembly //Speicherort des generischen Ressourcenwörterbuchs 39 | //(wird verwendet, wenn eine Ressource auf der Seite, in der Anwendung oder einem 40 | // designspezifischen Ressourcenwörterbuch nicht gefunden werden kann.) 41 | )] 42 | 43 | 44 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 45 | // 46 | // Hauptversion 47 | // Nebenversion 48 | // Buildnummer 49 | // Revision 50 | // 51 | // Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern 52 | // übernehmen, indem Sie "*" eingeben: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /Demo/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Dieser Code wurde von einem Tool generiert. 4 | // Laufzeitversion:4.0.30319.269 5 | // 6 | // Änderungen an dieser Datei können fehlerhaftes Verhalten verursachen und gehen verloren, wenn 7 | // der Code neu generiert wird. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Demo.Properties 12 | { 13 | 14 | 15 | /// 16 | /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. 17 | /// 18 | // Diese Klasse wurde von der StronglyTypedResourceBuilder-Klasse 19 | // über ein Tool wie ResGen oder Visual Studio automatisch generiert. 20 | // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen 21 | // mit der Option /str erneut aus, oder erstellen Sie Ihr VS-Projekt neu. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Demo.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle 56 | /// Ressourcenlookups, die diese stark typisierte Ressourcenklasse verwenden. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Demo/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /Demo/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.269 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Demo.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings) (global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Demo/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Demo/TypeTemplateSelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | 7 | namespace Demo 8 | { 9 | /// 10 | /// Provides a configurable DataTemplateSelector. 11 | /// 12 | [Obfuscation(Exclude = true, ApplyToMembers = true, Feature = "renaming")] 13 | public class TypeTemplateSelector : DataTemplateSelector 14 | { 15 | /// 16 | /// Gets or sets the list of template definitions. 17 | /// 18 | public List TemplateDefinitions { get; set; } 19 | 20 | /// 21 | /// Initialises a new instance of the TypeTemplateSelector class. 22 | /// 23 | public TypeTemplateSelector() 24 | { 25 | TemplateDefinitions = new List(); 26 | } 27 | 28 | /// 29 | /// Selects a DataTemplate for the item, based on the item's type. 30 | /// 31 | /// Item to select the template for. 32 | /// Unused. 33 | /// 34 | public override DataTemplate SelectTemplate(object item, DependencyObject container) 35 | { 36 | foreach (var def in TemplateDefinitions) 37 | { 38 | if (def.Type.IsInstanceOfType(item)) return def.Template; 39 | } 40 | return null; 41 | } 42 | } 43 | 44 | /// 45 | /// Defines a template to be selected for a type. 46 | /// 47 | [Obfuscation(Exclude = true, ApplyToMembers = true, Feature = "renaming")] 48 | public class TypeTemplateDefinition 49 | { 50 | /// 51 | /// Gets or sets the item type to define the template for. 52 | /// 53 | public Type Type { get; set; } 54 | /// 55 | /// Gets or sets the DataTemplate to select for this item type. 56 | /// 57 | public DataTemplate Template { get; set; } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Demo/ViewModel/ColorItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Windows.Media; 7 | 8 | namespace Demo.ViewModel 9 | { 10 | [Obfuscation(Exclude = true, ApplyToMembers = false, Feature = "renaming")] 11 | public class ColorItemViewModel : TreeItemViewModel 12 | { 13 | #region Constructor 14 | 15 | public ColorItemViewModel(TreeItemViewModel parent, bool lazyLoadChildren) 16 | : base(parent, lazyLoadChildren) 17 | { 18 | Color = Colors.Silver; 19 | } 20 | 21 | #endregion Constructor 22 | 23 | #region Public properties 24 | 25 | private Color color; 26 | public Color Color 27 | { 28 | get { return color; } 29 | set 30 | { 31 | if (value != color) 32 | { 33 | color = value; 34 | OnPropertyChanged("Color"); 35 | OnPropertyChanged("BackgroundBrush"); 36 | OnPropertyChanged("ForegroundBrush"); 37 | DisplayName = color.ToString(); 38 | } 39 | } 40 | } 41 | 42 | public Brush BackgroundBrush 43 | { 44 | get { return new SolidColorBrush(Color); } 45 | } 46 | 47 | public Brush ForegroundBrush 48 | { 49 | get { return IsDarkColor(Color) ? Brushes.White : Brushes.Black; } 50 | } 51 | 52 | #endregion Public properties 53 | 54 | #region Private methods 55 | 56 | /// 57 | /// Computes the grey value value of a color. 58 | /// 59 | /// 60 | /// 61 | private static byte ToGray(Color c) 62 | { 63 | return (byte) (c.R * 0.3 + c.G * 0.59 + c.B * 0.11); 64 | } 65 | 66 | /// 67 | /// Determines whether the color is dark or light. 68 | /// 69 | /// 70 | /// 71 | private static bool IsDarkColor(Color c) 72 | { 73 | return ToGray(c) < 0x90; 74 | } 75 | 76 | #endregion Private methods 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Demo/ViewModel/TreeItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.ComponentModel; 3 | using System.Reflection; 4 | 5 | namespace Demo.ViewModel 6 | { 7 | /// 8 | /// Sample base class for tree items view models. All specialised tree item view model classes 9 | /// should inherit from this class. 10 | /// 11 | [Obfuscation(Exclude = true, ApplyToMembers = false, Feature = "renaming")] 12 | public class TreeItemViewModel : INotifyPropertyChanged 13 | { 14 | #region Data 15 | 16 | private static readonly TreeItemViewModel DummyChild = new TreeItemViewModel(); 17 | 18 | private readonly ObservableCollection children; 19 | private readonly TreeItemViewModel parent; 20 | 21 | private bool isExpanded; 22 | private bool isSelected; 23 | private bool isEditable; 24 | private bool isEditing; 25 | private bool isEnabled = true; 26 | private bool isVisible = true; 27 | private string remarks; 28 | 29 | #endregion Data 30 | 31 | #region Constructor 32 | 33 | public TreeItemViewModel(TreeItemViewModel parent, bool lazyLoadChildren) 34 | { 35 | this.parent = parent; 36 | 37 | children = new ObservableCollection(); 38 | 39 | if (lazyLoadChildren) 40 | children.Add(DummyChild); 41 | } 42 | 43 | // This is used to create the DummyChild instance. 44 | private TreeItemViewModel() 45 | { 46 | } 47 | 48 | #endregion Constructor 49 | 50 | #region Public properties 51 | 52 | /// 53 | /// Returns the logical child items of this object. 54 | /// 55 | public ObservableCollection Children 56 | { 57 | get { return children; } 58 | } 59 | 60 | /// 61 | /// Returns true if this object's Children have not yet been populated. 62 | /// 63 | public bool HasDummyChild 64 | { 65 | get { return Children.Count == 1 && Children[0] == DummyChild; } 66 | } 67 | 68 | /// 69 | /// Gets/sets whether the TreeViewItem 70 | /// associated with this object is expanded. 71 | /// 72 | public bool IsExpanded 73 | { 74 | get { return isExpanded; } 75 | set 76 | { 77 | if (value != isExpanded) 78 | { 79 | isExpanded = value; 80 | OnPropertyChanged("IsExpanded"); 81 | 82 | // Expand all the way up to the root. 83 | if (isExpanded && parent != null) 84 | parent.IsExpanded = true; 85 | 86 | // Lazy load the child items, if necessary. 87 | if (isExpanded && HasDummyChild) 88 | { 89 | Children.Remove(DummyChild); 90 | LoadChildren(); 91 | } 92 | } 93 | } 94 | } 95 | 96 | /// 97 | /// Invoked when the child items need to be loaded on demand. 98 | /// Subclasses can override this to populate the Children collection. 99 | /// 100 | protected virtual void LoadChildren() 101 | { 102 | for (int i = 0; i < 100; i++) 103 | { 104 | Children.Add(new TreeItemViewModel(this, true) { DisplayName = "subnode " + i }); 105 | } 106 | } 107 | 108 | /// 109 | /// Gets/sets whether the TreeViewItem 110 | /// associated with this object is selected. 111 | /// 112 | public bool IsSelected 113 | { 114 | get { return isSelected; } 115 | set 116 | { 117 | if (value != isSelected) 118 | { 119 | isSelected = value; 120 | OnPropertyChanged("IsSelected"); 121 | } 122 | } 123 | } 124 | 125 | public bool IsEditable 126 | { 127 | get { return isEditable; } 128 | set 129 | { 130 | if (value != isEditable) 131 | { 132 | isEditable = value; 133 | OnPropertyChanged("IsEditable"); 134 | } 135 | } 136 | } 137 | 138 | public bool IsEditing 139 | { 140 | get { return isEditing; } 141 | set 142 | { 143 | if (value != isEditing) 144 | { 145 | isEditing = value; 146 | OnPropertyChanged("IsEditing"); 147 | } 148 | } 149 | } 150 | 151 | public bool IsEnabled 152 | { 153 | get { return isEnabled; } 154 | set 155 | { 156 | if (value != isEnabled) 157 | { 158 | isEnabled = value; 159 | OnPropertyChanged("IsEnabled"); 160 | } 161 | } 162 | } 163 | 164 | public bool IsVisible 165 | { 166 | get { return isVisible; } 167 | set 168 | { 169 | if (value != isVisible) 170 | { 171 | isVisible = value; 172 | OnPropertyChanged("IsVisible"); 173 | } 174 | } 175 | } 176 | 177 | public string Remarks 178 | { 179 | get { return remarks; } 180 | set 181 | { 182 | if (value != remarks) 183 | { 184 | remarks = value; 185 | OnPropertyChanged("Remarks"); 186 | } 187 | } 188 | } 189 | 190 | public TreeItemViewModel Parent 191 | { 192 | get { return parent; } 193 | } 194 | 195 | public override string ToString() 196 | { 197 | return "{Node " + DisplayName + "}"; 198 | } 199 | 200 | #endregion Public properties 201 | 202 | #region ViewModelBase 203 | 204 | /// 205 | /// Returns the user-friendly name of this object. 206 | /// Child classes can set this property to a new value, 207 | /// or override it to determine the value on-demand. 208 | /// 209 | private string displayName; 210 | public virtual string DisplayName 211 | { 212 | get { return displayName; } 213 | set 214 | { 215 | if (value != displayName) 216 | { 217 | displayName = value; 218 | OnPropertyChanged("DisplayName"); 219 | } 220 | } 221 | } 222 | 223 | #endregion ViewModelBase 224 | 225 | #region INotifyPropertyChanged members 226 | 227 | /// 228 | /// Raised when a property on this object has a new value. 229 | /// 230 | public event PropertyChangedEventHandler PropertyChanged; 231 | 232 | /// 233 | /// Raises this object's PropertyChanged event. 234 | /// 235 | /// The property that has a new value. 236 | protected void OnPropertyChanged(string propertyName) 237 | { 238 | var handler = PropertyChanged; 239 | if (handler != null) 240 | { 241 | handler(this, new PropertyChangedEventArgs(propertyName)); 242 | } 243 | } 244 | 245 | #endregion INotifyPropertyChanged members 246 | } 247 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | 4 | Copyright (c) 2012 Yves Goergen, Goroll 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/BindingTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using MultiSelectTreeView.Test.Model.Helper; 7 | using System.Windows.Automation; 8 | using System.Threading; 9 | 10 | namespace MultiSelectTreeView.Test.Model 11 | { 12 | [TestClass] 13 | public class BindingTest 14 | { 15 | [TestMethod] 16 | public void KeyboardLeftRightUpDownArrow() 17 | { 18 | using (TreeApplication app = new TreeApplication("BindingSample")) 19 | { 20 | BindingTrees trees = new BindingTrees(app); 21 | trees.LeftTree.Element1.Select(); 22 | Keyboard.Right(); 23 | Keyboard.Down(); 24 | trees.RightTree.Element1.Select(); 25 | Keyboard.Right(); 26 | Keyboard.Down(); 27 | //Assert.IsFalse(trees.LeftTree.Element11.IsFocused, "Left Element11 has not focus after down"); //does not work, because asking for IsFocused sets focus????? 28 | Assert.IsTrue(trees.LeftTree.Element11.IsSelected, "Left Element11 selected after down"); 29 | Assert.IsTrue(trees.RightTree.Element11.IsFocused, "Right Element11 has focus after down"); 30 | Assert.IsTrue(trees.RightTree.Element11.IsSelected, "Right Element11 selected after down"); 31 | } 32 | } 33 | 34 | [TestMethod] 35 | public void KeyboardUpDownWithShiftArrow() 36 | { 37 | using (TreeApplication app = new TreeApplication("SimpleSample")) 38 | { 39 | SimpleSampleTree sst = new SimpleSampleTree(app); 40 | sst.Element1.Select(); 41 | Keyboard.Right(); 42 | Keyboard.Down(); 43 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after down"); 44 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 has not focus after down"); 45 | 46 | Keyboard.ShiftDown(); 47 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after second down"); 48 | Assert.IsTrue(sst.Element12.IsSelected, "Element12 not selected after second down"); 49 | Assert.IsTrue(sst.Element12.IsFocused, "Element12 has not focus after second down"); 50 | 51 | Keyboard.Down(); 52 | Assert.IsFalse(sst.Element11.IsSelected, "Element11 selected after second down"); 53 | Assert.IsFalse(sst.Element12.IsSelected, "Element12 selected after second down"); 54 | Assert.IsTrue(sst.Element13.IsSelected, "Element13 not selected after down"); 55 | Assert.IsTrue(sst.Element13.IsFocused, "Element13 has not focus after down"); 56 | 57 | Keyboard.ShiftUp(); 58 | Keyboard.ShiftUp(); 59 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 selected after up"); 60 | Assert.IsTrue(sst.Element12.IsSelected, "Element12 selected after up"); 61 | Assert.IsTrue(sst.Element13.IsSelected, "Element13 not selected after up"); 62 | Assert.IsTrue(sst.Element11.IsFocused, "Element13 has not focus after up"); 63 | 64 | 65 | Keyboard.ShiftDown(); 66 | Keyboard.ShiftDown(); 67 | Keyboard.ShiftDown(); 68 | Assert.IsTrue(sst.Element13.IsSelected, "Element13 not selected after fourth down"); 69 | Assert.IsTrue(sst.Element14.IsSelected, "Element14 not selected after fourth down"); 70 | Assert.IsTrue(sst.Element14.IsFocused, "Element14 has not focus after fourth down"); 71 | } 72 | } 73 | 74 | [TestMethod] 75 | public void KeyboardUpDownWithCtrlArrow() 76 | { 77 | using (TreeApplication app = new TreeApplication("SimpleSample")) 78 | { 79 | SimpleSampleTree sst = new SimpleSampleTree(app); 80 | sst.Element1.Select(); 81 | Keyboard.Right(); 82 | Keyboard.Down(); 83 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after down"); 84 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 not has focus after down"); 85 | 86 | Keyboard.CtrlDown(); 87 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after ctrldown"); 88 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 not has focus after ctrldown"); 89 | Assert.IsTrue(sst.Element12.IsSelectedPreview, "Element12 not selectedPreview after ctrldown"); 90 | 91 | Keyboard.CtrlDown(); 92 | Keyboard.CtrlDown(); 93 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after ctrldowndowndown"); 94 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 not has focus after ctrldowndowndown"); 95 | Assert.IsTrue(sst.Element14.IsSelectedPreview, "Element12 not selectedPreview after ctrldowndowndown"); 96 | 97 | Keyboard.CtrlSpace(); 98 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after ctrlspace"); 99 | Assert.IsTrue(sst.Element14.IsFocused, "Element14 not has focus after ctrlspace"); 100 | Assert.IsTrue(sst.Element14.IsSelected, "Element14 not selected after ctrlspace"); 101 | Assert.IsFalse(sst.Element14.IsSelectedPreview, "Element14 selectedPreview after ctrlspace"); 102 | 103 | Keyboard.CtrlDown(); 104 | Keyboard.Space(); 105 | Assert.IsTrue(sst.Element15.IsSelected, "Element15 not selected after space"); 106 | Assert.IsTrue(sst.Element15.IsFocused, "Element15 not has focus after space"); 107 | } 108 | } 109 | 110 | #region Public Properties 111 | 112 | public TestContext TestContext { get; set; } 113 | 114 | #endregion 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/ExpandCollapseTests.cs: -------------------------------------------------------------------------------- 1 | namespace MultiSelectTreeView.Test.Model 2 | { 3 | #region 4 | using System.Windows.Automation; 5 | 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | using MultiSelectTreeView.Test.Model.Helper; 9 | 10 | #endregion 11 | 12 | [TestClass] 13 | [DeploymentItem("W7StyleSample.exe")] 14 | public class ExpandCollapseTests 15 | { 16 | #region Constants and Fields 17 | 18 | private const string FileName = "W7StyleSample.exe"; 19 | 20 | private const string ProcessName = "W7StyleSample"; 21 | 22 | #endregion 23 | 24 | #region Public Properties 25 | 26 | public TestContext TestContext { get; set; } 27 | 28 | #endregion 29 | 30 | #region Public Methods 31 | 32 | [TestMethod] 33 | public void Collapse() 34 | { 35 | using (TreeApplication app = new TreeApplication("SimpleSample")) 36 | { 37 | SimpleSampleTree sst = new SimpleSampleTree(app); 38 | sst.Element1.Expand(); 39 | sst.Element1.Collapse(); 40 | 41 | Assert.IsFalse(sst.Element1.IsExpanded); 42 | } 43 | } 44 | 45 | [TestMethod] 46 | public void Expand() 47 | { 48 | using (TreeApplication app = new TreeApplication("SimpleSample")) 49 | { 50 | SimpleSampleTree sst = new SimpleSampleTree(app); 51 | sst.Element1.Expand(); 52 | 53 | Assert.IsTrue(sst.Element1.IsExpanded); 54 | } 55 | } 56 | 57 | #endregion 58 | } 59 | } -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/Helper/AutomationElementHelper.cs: -------------------------------------------------------------------------------- 1 | namespace MultiSelectTreeView.Test.Model.Helper 2 | { 3 | #region 4 | 5 | using System; 6 | using System.Windows.Automation; 7 | using System.Linq; 8 | #endregion 9 | 10 | /// 11 | /// Helper methods for AutomationElements 12 | /// 13 | public static class AutomationElementHelper 14 | { 15 | #region Methods 16 | 17 | public static AutomationPattern GetPattern() where T : BasePattern 18 | { 19 | if (typeof(T) == typeof(ExpandCollapsePattern)) 20 | { 21 | return ExpandCollapsePattern.Pattern; 22 | } 23 | 24 | if (typeof(T) == typeof(SelectionItemPattern)) 25 | { 26 | return SelectionItemPattern.Pattern; 27 | } 28 | 29 | if (typeof(T) == typeof(ValuePattern)) 30 | { 31 | return ValuePattern.Pattern; 32 | } 33 | 34 | throw new InvalidOperationException("Pattern not supported of GetPattern extension method."); 35 | } 36 | 37 | public static T GetPattern(this AutomationElement treeItem) where T : BasePattern 38 | { 39 | T t = treeItem.GetCurrentPattern(GetPattern()) as T; 40 | if (t == null) 41 | { 42 | throw new InvalidOperationException("Pattern was not found for object."); 43 | } 44 | 45 | return t; 46 | } 47 | 48 | public static AutomationElement FindFirstDescendant(this AutomationElement element, ControlType type) 49 | { 50 | PropertyCondition cond = new PropertyCondition(AutomationElement.ControlTypeProperty, type); 51 | 52 | AutomationElement treeItem = element.FindFirst(TreeScope.Descendants, cond); 53 | 54 | return treeItem; 55 | } 56 | 57 | public static AutomationElement FindDescendant(this AutomationElement element, ControlType type, int index) 58 | { 59 | PropertyCondition cond = new PropertyCondition(AutomationElement.ControlTypeProperty, type); 60 | 61 | AutomationElementCollection treeItems = element.FindAll(TreeScope.Descendants, cond); 62 | 63 | return treeItems[index]; 64 | } 65 | 66 | public static AutomationElement FindDescendantByName(this AutomationElement element, ControlType type, string name) 67 | { 68 | PropertyCondition typeCond = new PropertyCondition(AutomationElement.ControlTypeProperty, type); 69 | PropertyCondition nameCond = new PropertyCondition(AutomationElement.NameProperty, name); 70 | AndCondition and = new AndCondition(new Condition[] { typeCond, nameCond }); 71 | 72 | return element.FindFirst(TreeScope.Descendants, and); 73 | } 74 | 75 | public static AutomationElement FindDescendantByAutomationId(this AutomationElement element, ControlType type, string automationId) 76 | { 77 | PropertyCondition typeCond = new PropertyCondition(AutomationElement.ControlTypeProperty, type); 78 | PropertyCondition nameCond = new PropertyCondition(AutomationElement.AutomationIdProperty, automationId); 79 | AndCondition and = new AndCondition(new Condition[] { typeCond, nameCond }); 80 | 81 | return element.FindFirst(TreeScope.Descendants, and); 82 | } 83 | #endregion 84 | } 85 | } -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/Helper/Keyboard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows.Forms; 6 | using System.Runtime.InteropServices; 7 | using System.Threading; 8 | using System.Windows.Input; 9 | using System.ComponentModel; 10 | 11 | namespace MultiSelectTreeView.Test.Model.Helper 12 | { 13 | static class Keyboard 14 | { 15 | public static void Right() 16 | { 17 | Press(Key.Right); 18 | } 19 | 20 | public static void Left() 21 | { 22 | Press(Key.Left); 23 | } 24 | 25 | public static void Up() 26 | { 27 | Press(Key.Up); 28 | } 29 | 30 | public static void Down() 31 | { 32 | Press(Key.Down); 33 | } 34 | 35 | public static void ShiftUp() 36 | { 37 | Press(Key.Up, Key.LeftShift); 38 | } 39 | 40 | public static void ShiftDown() 41 | { 42 | Press(Key.Down, Key.LeftShift); 43 | } 44 | 45 | public static void CtrlUp() 46 | { 47 | Press(Key.Up, Key.LeftCtrl); 48 | } 49 | 50 | public static void CtrlDown() 51 | { 52 | Press(Key.Down, Key.LeftCtrl); 53 | } 54 | 55 | public static void Space() 56 | { 57 | Press(Key.Space); 58 | } 59 | 60 | public static void CtrlSpace() 61 | { 62 | Press(Key.Space, Key.LeftCtrl); 63 | } 64 | 65 | public static void HoldShift() 66 | { 67 | Hold(Key.LeftShift); 68 | } 69 | 70 | public static void ReleaseShift() 71 | { 72 | Release(Key.LeftShift); 73 | } 74 | 75 | public static void HoldCtrl() 76 | { 77 | Hold(Key.LeftCtrl); 78 | } 79 | 80 | public static void ReleaseCtrl() 81 | { 82 | Release(Key.LeftCtrl); 83 | } 84 | 85 | #region Private methods 86 | private static void Hold(Key key) 87 | { 88 | SendKeyboardInput(key, true); 89 | SleepBetween(); 90 | } 91 | 92 | private static void Release(Key key) 93 | { 94 | SendKeyboardInput(key, false); 95 | SleepAfter(); 96 | } 97 | 98 | private static void Press(Key key) 99 | { 100 | SendKeyboardInput(key, true); 101 | SleepBetween(); 102 | SendKeyboardInput(key, false); 103 | SleepAfter(); 104 | } 105 | 106 | private static void Press(Key key, Key modifierKey) 107 | { 108 | SendKeyboardInput(modifierKey, true); 109 | SendKeyboardInput(key, true); 110 | SleepBetween(); 111 | SleepBetween(); 112 | SendKeyboardInput(key, false); 113 | SendKeyboardInput(modifierKey, false); 114 | SleepAfter(); 115 | } 116 | 117 | private static void SleepBetween() 118 | { 119 | Thread.Sleep(10); 120 | } 121 | 122 | private static void SleepAfter() 123 | { 124 | Thread.Sleep(500); 125 | } 126 | 127 | /// 128 | /// Inject keyboard input into the system 129 | /// 130 | /// indicates the key pressed or released. Can be one of the constants defined in the Key enum 131 | /// true to inject a key press, false to inject a key release 132 | /// 133 | /// 134 | /// This API does not work inside the secure execution environment. 135 | /// 136 | /// 137 | private static void SendKeyboardInput(Key key, bool press) 138 | { 139 | //CASRemoval:AutomationPermission.Demand( AutomationPermissionFlag.Input ); 140 | 141 | INPUT ki = new INPUT(); 142 | ki.type = Const.INPUT_KEYBOARD; 143 | ki.union.keyboardInput.wVk = (short)KeyInterop.VirtualKeyFromKey(key); 144 | ki.union.keyboardInput.wScan = (short)MapVirtualKey(ki.union.keyboardInput.wVk, 0); 145 | int dwFlags = 0; 146 | if (ki.union.keyboardInput.wScan > 0) 147 | dwFlags |= KEYEVENTF_SCANCODE; 148 | if (!press) 149 | dwFlags |= KEYEVENTF_KEYUP; 150 | ki.union.keyboardInput.dwFlags = dwFlags; 151 | if (IsExtendedKey(key)) 152 | { 153 | ki.union.keyboardInput.dwFlags |= KEYEVENTF_EXTENDEDKEY; 154 | } 155 | ki.union.keyboardInput.time = 0; 156 | ki.union.keyboardInput.dwExtraInfo = new IntPtr(0); 157 | if (SendInput(1, ref ki, Marshal.SizeOf(ki)) == 0) 158 | throw new Win32Exception(Marshal.GetLastWin32Error()); 159 | } 160 | 161 | private static bool IsExtendedKey(Key key) 162 | { 163 | // From the SDK: 164 | // The extended-key flag indicates whether the keystroke message originated from one of 165 | // the additional keys on the enhanced keyboard. The extended keys consist of the ALT and 166 | // CTRL keys on the right-hand side of the keyboard; the INS, DEL, HOME, END, PAGE UP, 167 | // PAGE DOWN, and arrow keys in the clusters to the left of the numeric keypad; the NUM LOCK 168 | // key; the BREAK (CTRL+PAUSE) key; the PRINT SCRN key; and the divide (/) and ENTER keys in 169 | // the numeric keypad. The extended-key flag is set if the key is an extended key. 170 | // 171 | // - docs appear to be incorrect. Use of Spy++ indicates that break is not an extended key. 172 | // Also, menu key and windows keys also appear to be extended. 173 | return key == Key.RightAlt 174 | || key == Key.RightCtrl 175 | || key == Key.NumLock 176 | || key == Key.Insert 177 | || key == Key.Delete 178 | || key == Key.Home 179 | || key == Key.End 180 | || key == Key.Prior 181 | || key == Key.Next 182 | || key == Key.Up 183 | || key == Key.Down 184 | || key == Key.Left 185 | || key == Key.Right 186 | || key == Key.Apps 187 | || key == Key.RWin 188 | || key == Key.LWin; 189 | 190 | // Note that there are no distinct values for the following keys: 191 | // numpad divide 192 | // numpad enter 193 | } 194 | #endregion 195 | 196 | #region Constants 197 | public const int KEYEVENTF_EXTENDEDKEY = 0x0001; 198 | public const int KEYEVENTF_KEYUP = 0x0002; 199 | public const int KEYEVENTF_SCANCODE = 0x0008; 200 | #endregion 201 | 202 | #region Native methods 203 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 204 | public static extern int MapVirtualKey(int nVirtKey, int nMapType); 205 | 206 | [DllImport("user32.dll", SetLastError = true)] 207 | public static extern int SendInput(int nInputs, ref INPUT mi, int cbSize); 208 | #endregion 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/Helper/Mouse.cs: -------------------------------------------------------------------------------- 1 | namespace MultiSelectTreeView.Test.Model.Helper 2 | { 3 | using System; 4 | using System.ComponentModel; 5 | using System.Runtime.InteropServices; 6 | using System.Threading; 7 | using System.Windows; 8 | using System.Windows.Automation; 9 | using Microsoft.VisualStudio.TestTools.UnitTesting; 10 | 11 | /// 12 | /// Class for Keyboard use 13 | /// 14 | public static class Mouse 15 | { 16 | #region Constants 17 | const int SM_SWAPBUTTON = 23; 18 | const int SM_XVIRTUALSCREEN = 76; 19 | const int SM_YVIRTUALSCREEN = 77; 20 | const int SM_CXVIRTUALSCREEN = 78; 21 | const int SM_CYVIRTUALSCREEN = 79; 22 | const int MOUSEEVENTF_VIRTUALDESK = 0x4000; 23 | #endregion 24 | 25 | #region Specific tree methods 26 | internal static void ExpandCollapseClick(Element element) 27 | { 28 | bool oldExpandState = element.IsExpanded; 29 | AutomationElement toggleButton = element.Ae.FindDescendantByAutomationId(ControlType.Button, "Expander"); 30 | 31 | try 32 | { 33 | Click(toggleButton.GetClickablePoint()); 34 | } 35 | catch 36 | { 37 | } 38 | SleepAfter(); 39 | 40 | if (oldExpandState == element.IsExpanded) 41 | throw new InvalidOperationException("Changing expand state did not work"); 42 | } 43 | 44 | internal static void Click(Element element) 45 | { 46 | Click(element.Ae); 47 | SleepAfter(); 48 | } 49 | 50 | internal static void ShiftClick(Element element) 51 | { 52 | Keyboard.HoldShift(); 53 | Click(element.Ae); 54 | SleepBetween(); 55 | Keyboard.ReleaseShift(); 56 | } 57 | 58 | internal static void CtrlClick(Element element) 59 | { 60 | Keyboard.HoldCtrl(); 61 | Click(element.Ae); 62 | SleepBetween(); 63 | Keyboard.ReleaseCtrl(); 64 | } 65 | 66 | private static void SleepAfter() 67 | { 68 | Thread.Sleep(500); 69 | } 70 | 71 | private static void SleepBetween() 72 | { 73 | Thread.Sleep(10); 74 | } 75 | #endregion 76 | 77 | #region Private methods 78 | /// 79 | /// Inject pointer input into the system 80 | /// 81 | /// x coordinate of pointer, if Move flag specified 82 | /// y coordinate of pointer, if Move flag specified 83 | /// wheel movement, or mouse X button, depending on flags 84 | /// flags to indicate which type of input occurred - move, button press/release, wheel move, etc. 85 | /// x, y are in pixels. If Absolute flag used, are relative to desktop origin. 86 | /// 87 | /// This API does not work inside the secure execution environment. 88 | /// 89 | /// 90 | private static void SendMouseInput(double x, double y, int data, SendMouseInputFlags flags) 91 | { 92 | //CASRemoval:AutomationPermission.Demand( AutomationPermissionFlag.Input ); 93 | 94 | int intflags = (int)flags; 95 | 96 | if ((intflags & (int)SendMouseInputFlags.Absolute) != 0) 97 | { 98 | int vscreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); 99 | int vscreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); 100 | int vscreenLeft = GetSystemMetrics(SM_XVIRTUALSCREEN); 101 | int vscreenTop = GetSystemMetrics(SM_YVIRTUALSCREEN); 102 | 103 | // Absolute input requires that input is in 'normalized' coords - with the entire 104 | // desktop being (0,0)...(65535,65536). Need to convert input x,y coords to this 105 | // first. 106 | // 107 | // In this normalized world, any pixel on the screen corresponds to a block of values 108 | // of normalized coords - eg. on a 1024x768 screen, 109 | // y pixel 0 corresponds to range 0 to 85.333, 110 | // y pixel 1 corresponds to range 85.333 to 170.666, 111 | // y pixel 2 correpsonds to range 170.666 to 256 - and so on. 112 | // Doing basic scaling math - (x-top)*65536/Width - gets us the start of the range. 113 | // However, because int math is used, this can end up being rounded into the wrong 114 | // pixel. For example, if we wanted pixel 1, we'd get 85.333, but that comes out as 115 | // 85 as an int, which falls into pixel 0's range - and that's where the pointer goes. 116 | // To avoid this, we add on half-a-"screen pixel"'s worth of normalized coords - to 117 | // push us into the middle of any given pixel's range - that's the 65536/(Width*2) 118 | // part of the formula. So now pixel 1 maps to 85+42 = 127 - which is comfortably 119 | // in the middle of that pixel's block. 120 | // The key ting here is that unlike points in coordinate geometry, pixels take up 121 | // space, so are often better treated like rectangles - and if you want to target 122 | // a particular pixel, target its rectangle's midpoint, not its edge. 123 | x = ((x - vscreenLeft) * 65536) / vscreenWidth + 65536 / (vscreenWidth * 2); 124 | y = ((y - vscreenTop) * 65536) / vscreenHeight + 65536 / (vscreenHeight * 2); 125 | 126 | intflags |= MOUSEEVENTF_VIRTUALDESK; 127 | } 128 | 129 | INPUT mi = new INPUT(); 130 | mi.type = Const.INPUT_MOUSE; 131 | mi.union.mouseInput.dx = (int)x; 132 | mi.union.mouseInput.dy = (int)y; 133 | mi.union.mouseInput.mouseData = data; 134 | mi.union.mouseInput.dwFlags = intflags; 135 | mi.union.mouseInput.time = 0; 136 | mi.union.mouseInput.dwExtraInfo = new IntPtr(0); 137 | //Console.WriteLine("Sending"); 138 | if (SendInput(1, ref mi, Marshal.SizeOf(mi)) == 0) 139 | throw new Win32Exception(Marshal.GetLastWin32Error()); 140 | } 141 | #endregion 142 | 143 | #region Public methods 144 | public static Point Loaction() 145 | { 146 | Win32Point w32Mouse = new Win32Point(); 147 | GetCursorPos(ref w32Mouse); 148 | return new Point(w32Mouse.X, w32Mouse.Y); 149 | } 150 | 151 | /// 152 | /// Move the mouse to a point. 153 | /// 154 | /// The point that the mouse will move to. 155 | /// pt are in pixels that are relative to desktop origin. 156 | /// 157 | /// 158 | /// This API does not work inside the secure execution environment. 159 | /// 160 | /// 161 | public static void MoveTo(Point pt) 162 | { 163 | SendMouseInput(pt.X, pt.Y, 0, SendMouseInputFlags.Move | SendMouseInputFlags.Absolute); 164 | } 165 | 166 | /// 167 | /// Move the mouse to an element. 168 | /// 169 | /// The element that the mouse will move to 170 | /// If there is not clickable point for the element 171 | /// 172 | /// 173 | /// This API does not work inside the secure execution environment. 174 | /// 175 | /// 176 | public static void MoveTo(AutomationElement el) 177 | { 178 | if (el == null) 179 | { 180 | throw new ArgumentNullException("el"); 181 | } 182 | MoveTo(el.GetClickablePoint()); 183 | } 184 | 185 | /// 186 | /// Move the mouse to a point and click. The primary mouse button will be used 187 | /// this is usually the left button except if the mouse buttons are swaped. 188 | /// 189 | /// The point to click at 190 | /// pt are in pixels that are relative to desktop origin. 191 | /// 192 | /// 193 | /// This API does not work inside the secure execution environment. 194 | /// 195 | /// 196 | public static void MoveToAndClick(Point pt) 197 | { 198 | SendMouseInput(pt.X, pt.Y, 0, SendMouseInputFlags.Move | SendMouseInputFlags.Absolute); 199 | 200 | RawClick(pt, true); 201 | } 202 | 203 | /// 204 | /// Move the mouse to a point and double click. The primary mouse button will be used 205 | /// this is usually the left button except if the mouse buttons are swaped. 206 | /// 207 | /// The point to click at 208 | /// pt are in pixels that are relative to desktop origin. 209 | /// 210 | /// 211 | /// This API does not work inside the secure execution environment. 212 | /// 213 | /// 214 | public static void MoveToAndDoubleClick(Point pt) 215 | { 216 | SendMouseInput(pt.X, pt.Y, 0, SendMouseInputFlags.Move | SendMouseInputFlags.Absolute); 217 | 218 | RawClick(pt, true); 219 | Thread.Sleep(300); 220 | RawClick(pt, true); 221 | } 222 | 223 | private static void RawClick(Point pt, bool left) 224 | { 225 | // send SendMouseInput works in term of the phisical mouse buttons, therefore we need 226 | // to check to see if the mouse buttons are swapped because this method need to use the primary 227 | // mouse button. 228 | if (ButtonsSwapped() != left) 229 | { 230 | // the mouse buttons are not swaped the primary is the left 231 | SendMouseInput(pt.X, pt.Y, 0, SendMouseInputFlags.LeftDown | SendMouseInputFlags.Absolute); 232 | SendMouseInput(pt.X, pt.Y, 0, SendMouseInputFlags.LeftUp | SendMouseInputFlags.Absolute); 233 | } 234 | else 235 | { 236 | // the mouse buttons are swaped so click the right button which as actually the primary 237 | SendMouseInput(pt.X, pt.Y, 0, SendMouseInputFlags.RightDown | SendMouseInputFlags.Absolute); 238 | SendMouseInput(pt.X, pt.Y, 0, SendMouseInputFlags.RightUp | SendMouseInputFlags.Absolute); 239 | } 240 | } 241 | 242 | private static bool ButtonsSwapped() 243 | { 244 | return GetSystemMetrics(SM_SWAPBUTTON) != 0; 245 | } 246 | 247 | public static void MoveToAndRightClick(Point pt) 248 | { 249 | SendMouseInput(pt.X, pt.Y, 0, SendMouseInputFlags.Move | SendMouseInputFlags.Absolute); 250 | 251 | RawClick(pt, false); 252 | } 253 | 254 | public static void Click(Point point) 255 | { 256 | MoveToAndClick(point); 257 | } 258 | 259 | private static void Click(AutomationElement element) 260 | { 261 | try 262 | { 263 | MoveToAndClick(element.GetClickablePoint()); 264 | } 265 | catch 266 | { 267 | } 268 | } 269 | 270 | internal static void DoubleClick(Element element) 271 | { 272 | DoubleClick(element.Ae); 273 | SleepAfter(); 274 | } 275 | 276 | private static void DoubleClick(AutomationElement element) 277 | { 278 | MoveToAndDoubleClick(element.GetClickablePoint()); 279 | } 280 | 281 | private static void RightClick(Point point) 282 | { 283 | MoveToAndRightClick(point); 284 | } 285 | #endregion 286 | 287 | #region Native types 288 | /// 289 | /// Flags for Input.SendMouseInput, indicate whether movent took place, 290 | /// or whether buttons were pressed or released. 291 | /// 292 | [Flags] 293 | public enum SendMouseInputFlags 294 | { 295 | /// Specifies that the pointer moved. 296 | Move = 0x0001, 297 | /// Specifies that the left button was pressed. 298 | LeftDown = 0x0002, 299 | /// Specifies that the left button was released. 300 | LeftUp = 0x0004, 301 | /// Specifies that the right button was pressed. 302 | RightDown = 0x0008, 303 | /// Specifies that the right button was released. 304 | RightUp = 0x0010, 305 | /// Specifies that the middle button was pressed. 306 | MiddleDown = 0x0020, 307 | /// Specifies that the middle button was released. 308 | MiddleUp = 0x0040, 309 | /// Specifies that the x button was pressed. 310 | XDown = 0x0080, 311 | /// Specifies that the x button was released. 312 | XUp = 0x0100, 313 | /// Specifies that the wheel was moved 314 | Wheel = 0x0800, 315 | /// Specifies that x, y are absolute, not relative 316 | Absolute = 0x8000, 317 | }; 318 | 319 | [StructLayout(LayoutKind.Sequential)] 320 | internal struct Win32Point 321 | { 322 | public Int32 X; 323 | public Int32 Y; 324 | }; 325 | #endregion 326 | 327 | #region Native methods 328 | [DllImport("user32.dll")] 329 | [return: MarshalAs(UnmanagedType.Bool)] 330 | internal static extern bool GetCursorPos(ref Win32Point pt); 331 | 332 | [DllImport("user32.dll")] 333 | public static extern int GetSystemMetrics(int metric); 334 | 335 | [DllImport("user32.dll", SetLastError = true)] 336 | public static extern int SendInput(int nInputs, ref INPUT mi, int cbSize); 337 | #endregion 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/Helper/NativeStructs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace MultiSelectTreeView.Test.Model.Helper 8 | { 9 | public class Const 10 | { 11 | public const int INPUT_MOUSE = 0; 12 | public const int INPUT_KEYBOARD = 1; 13 | } 14 | 15 | [StructLayout(LayoutKind.Sequential)] 16 | public struct INPUT 17 | { 18 | public int type; 19 | public INPUTUNION union; 20 | }; 21 | 22 | [StructLayout(LayoutKind.Explicit)] 23 | public struct INPUTUNION 24 | { 25 | [FieldOffset(0)] 26 | public MOUSEINPUT mouseInput; 27 | [FieldOffset(0)] 28 | public KEYBDINPUT keyboardInput; 29 | }; 30 | 31 | [StructLayout(LayoutKind.Sequential)] 32 | public struct MOUSEINPUT 33 | { 34 | public int dx; 35 | public int dy; 36 | public int mouseData; 37 | public int dwFlags; 38 | public int time; 39 | public IntPtr dwExtraInfo; 40 | }; 41 | 42 | [StructLayout(LayoutKind.Sequential)] 43 | public struct KEYBDINPUT 44 | { 45 | public short wVk; 46 | public short wScan; 47 | public int dwFlags; 48 | public int time; 49 | public IntPtr dwExtraInfo; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/Helper/TreeApplication.cs: -------------------------------------------------------------------------------- 1 | namespace MultiSelectTreeView.Test.Model.Helper 2 | { 3 | #region 4 | 5 | using System; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Threading; 9 | using System.Windows.Automation; 10 | using System.Runtime.InteropServices; 11 | 12 | #endregion 13 | 14 | /// 15 | /// Helper to handle a processes automation element 16 | /// 17 | public class TreeApplication : IDisposable 18 | { 19 | #region Constants and Fields 20 | 21 | private readonly string processName; 22 | 23 | private static Process p; 24 | 25 | private AutomationElement app; 26 | 27 | #endregion 28 | 29 | #region Constructors and Destructors 30 | 31 | public TreeApplication(string fileName) 32 | { 33 | this.processName = fileName;// +".exe"; 34 | 35 | p = GetProcess(processName); 36 | if (p != null) 37 | { 38 | p.Kill(); 39 | } 40 | 41 | ProcessStartInfo ps = new ProcessStartInfo { FileName = fileName }; 42 | p = new Process(); 43 | p.StartInfo = ps; 44 | p.Start(); 45 | 46 | while (!p.Responding) 47 | { 48 | Trace.WriteLine("waiting process"); 49 | Thread.Sleep(200); 50 | } 51 | 52 | while (app == null) 53 | { 54 | app = InitializeAutomationElement(); 55 | Trace.WriteLine("waiting for app"); 56 | Thread.Sleep(200); 57 | } 58 | } 59 | #endregion 60 | 61 | #region Public Properties 62 | 63 | public AutomationElement Ae 64 | { 65 | get 66 | { 67 | return app; 68 | } 69 | } 70 | 71 | #endregion 72 | 73 | #region Public Methods 74 | 75 | public void Dispose() 76 | { 77 | if (p != null && p.MainWindowHandle != IntPtr.Zero) 78 | { 79 | p.Kill(); 80 | while (GetProcess(processName) != null) 81 | { 82 | Thread.Sleep(50); 83 | } 84 | } 85 | } 86 | 87 | #endregion 88 | 89 | #region Methods 90 | 91 | private static Process GetProcess(string processname) 92 | { 93 | Process[] processes = Process.GetProcessesByName(processname); 94 | return processes.FirstOrDefault(); 95 | } 96 | 97 | private AutomationElement InitializeAutomationElement() 98 | { 99 | AutomationElement ae = AutomationElement.RootElement; 100 | PropertyCondition cond = new PropertyCondition(AutomationElement.ProcessIdProperty, p.Id); 101 | 102 | return ae.FindFirst(TreeScope.Children, cond); 103 | } 104 | 105 | #endregion 106 | } 107 | } -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/KeyboardReactionTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using MultiSelectTreeView.Test.Model.Helper; 7 | using System.Windows.Automation; 8 | using System.Threading; 9 | 10 | namespace MultiSelectTreeView.Test.Model 11 | { 12 | [TestClass] 13 | public class KeyboardReactionTest 14 | { 15 | [TestMethod] 16 | public void KeyboardLeftRightUpDownArrow() 17 | { 18 | using (TreeApplication app = new TreeApplication("SimpleSample")) 19 | { 20 | SimpleSampleTree sst = new SimpleSampleTree(app); 21 | sst.Element1.Select(); 22 | Keyboard.Right(); 23 | Assert.IsTrue(sst.Element1.IsExpanded, "Element1 not expanded"); 24 | 25 | Keyboard.Down(); 26 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 has not focus after down"); 27 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after down"); 28 | 29 | Keyboard.Down(); 30 | Assert.IsTrue(sst.Element12.IsFocused, "Element12 has not focus after down"); 31 | Assert.IsTrue(sst.Element12.IsSelected, "Element12 not selected after down"); 32 | 33 | Keyboard.Down(); 34 | Assert.IsTrue(sst.Element13.IsFocused, "Element13 has not focus after down"); 35 | Assert.IsTrue(sst.Element13.IsSelected, "Element13 not selected after down"); 36 | 37 | Keyboard.Right(); 38 | Assert.IsTrue(sst.Element13.IsExpanded, "Element13 not expanded"); 39 | 40 | Keyboard.Left(); 41 | Assert.IsFalse(sst.Element13.IsExpanded, "Element13 expanded"); 42 | 43 | Keyboard.Up(); 44 | Assert.IsTrue(sst.Element12.IsFocused, "Element12 has not focus after Up"); 45 | Assert.IsTrue(sst.Element12.IsSelected, "Element12 not selected after Up"); 46 | } 47 | } 48 | 49 | [TestMethod] 50 | public void KeyboardUpDownWithShiftArrow() 51 | { 52 | using (TreeApplication app = new TreeApplication("SimpleSample")) 53 | { 54 | SimpleSampleTree sst = new SimpleSampleTree(app); 55 | sst.Element1.Select(); 56 | Keyboard.Right(); 57 | Keyboard.Down(); 58 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after down"); 59 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 has not focus after down"); 60 | 61 | Keyboard.ShiftDown(); 62 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after second down"); 63 | Assert.IsTrue(sst.Element12.IsSelected, "Element12 not selected after second down"); 64 | Assert.IsTrue(sst.Element12.IsFocused, "Element12 has not focus after second down"); 65 | 66 | Keyboard.Down(); 67 | Assert.IsFalse(sst.Element11.IsSelected, "Element11 selected after second down"); 68 | Assert.IsFalse(sst.Element12.IsSelected, "Element12 selected after second down"); 69 | Assert.IsTrue(sst.Element13.IsSelected, "Element13 not selected after down"); 70 | Assert.IsTrue(sst.Element13.IsFocused, "Element13 has not focus after down"); 71 | 72 | Keyboard.ShiftUp(); 73 | Keyboard.ShiftUp(); 74 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 selected after up"); 75 | Assert.IsTrue(sst.Element12.IsSelected, "Element12 selected after up"); 76 | Assert.IsTrue(sst.Element13.IsSelected, "Element13 not selected after up"); 77 | Assert.IsTrue(sst.Element11.IsFocused, "Element13 has not focus after up"); 78 | 79 | 80 | Keyboard.ShiftDown(); 81 | Keyboard.ShiftDown(); 82 | Keyboard.ShiftDown(); 83 | Assert.IsTrue(sst.Element13.IsSelected, "Element13 not selected after fourth down"); 84 | Assert.IsTrue(sst.Element14.IsSelected, "Element14 not selected after fourth down"); 85 | Assert.IsTrue(sst.Element14.IsFocused, "Element14 has not focus after fourth down"); 86 | } 87 | } 88 | 89 | [TestMethod] 90 | public void KeyboardUpDownWithCtrlArrow() 91 | { 92 | using (TreeApplication app = new TreeApplication("SimpleSample")) 93 | { 94 | SimpleSampleTree sst = new SimpleSampleTree(app); 95 | sst.Element1.Select(); 96 | Keyboard.Right(); 97 | Keyboard.Down(); 98 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after down"); 99 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 not has focus after down"); 100 | 101 | Keyboard.CtrlDown(); 102 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after ctrldown"); 103 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 not has focus after ctrldown"); 104 | Assert.IsTrue(sst.Element12.IsSelectedPreview, "Element12 not selectedPreview after ctrldown"); 105 | 106 | Keyboard.CtrlDown(); 107 | Keyboard.CtrlDown(); 108 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after ctrldowndowndown"); 109 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 not has focus after ctrldowndowndown"); 110 | Assert.IsTrue(sst.Element14.IsSelectedPreview, "Element12 not selectedPreview after ctrldowndowndown"); 111 | 112 | Keyboard.CtrlSpace(); 113 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after ctrlspace"); 114 | Assert.IsTrue(sst.Element14.IsFocused, "Element14 not has focus after ctrlspace"); 115 | Assert.IsTrue(sst.Element14.IsSelected, "Element14 not selected after ctrlspace"); 116 | Assert.IsFalse(sst.Element14.IsSelectedPreview, "Element14 selectedPreview after ctrlspace"); 117 | 118 | Keyboard.CtrlDown(); 119 | Keyboard.Space(); 120 | Assert.IsTrue(sst.Element15.IsSelected, "Element15 not selected after space"); 121 | Assert.IsTrue(sst.Element15.IsFocused, "Element15 not has focus after space"); 122 | } 123 | } 124 | 125 | #region Public Properties 126 | 127 | public TestContext TestContext { get; set; } 128 | 129 | #endregion 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/Model/BindingTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using MultiSelectTreeView.Test.Model.Helper; 6 | using System.Windows.Automation; 7 | 8 | namespace MultiSelectTreeView.Test.Model 9 | { 10 | class BindingTrees 11 | { 12 | public BindingTrees(TreeApplication app) 13 | { 14 | LeftTree = new Tree(app.Ae.FindDescendantByAutomationId(ControlType.Tree, "leftTree")); 15 | RightTree = new Tree(app.Ae.FindDescendantByAutomationId(ControlType.Tree, "rightTree")); 16 | } 17 | 18 | public Tree LeftTree { get; set; } 19 | public Tree RightTree { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/Model/Element.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows.Automation; 6 | using MultiSelectTreeView.Test.Model.Helper; 7 | 8 | namespace MultiSelectTreeView.Test.Model 9 | { 10 | class Element 11 | { 12 | public Element(AutomationElement automationElement) 13 | { 14 | Ae = automationElement; 15 | } 16 | 17 | internal AutomationElement Ae { get; private set; } 18 | 19 | public void Expand() 20 | { 21 | ExpandCollapsePattern expandPattern = Ae.GetPattern(); 22 | expandPattern.Expand(); 23 | } 24 | 25 | public void Collapse() 26 | { 27 | ExpandCollapsePattern expandPattern = Ae.GetPattern(); 28 | expandPattern.Collapse(); 29 | } 30 | 31 | public void Select() 32 | { 33 | SelectionItemPattern pattern = Ae.GetPattern(); 34 | pattern.Select(); 35 | } 36 | 37 | public bool IsSelected 38 | { 39 | get 40 | { 41 | SelectionItemPattern pattern = Ae.GetPattern(); 42 | return pattern.Current.IsSelected; 43 | } 44 | } 45 | 46 | public bool IsExpanded 47 | { 48 | get 49 | { 50 | ExpandCollapsePattern pattern = Ae.GetPattern(); 51 | return pattern.Current.ExpandCollapseState == ExpandCollapseState.Expanded; 52 | } 53 | } 54 | 55 | public bool IsFocused 56 | { 57 | get 58 | { 59 | return Convert.ToBoolean(GetValue("node;IsFocused")); 60 | } 61 | } 62 | 63 | public bool IsSelectedPreview 64 | { 65 | get 66 | { 67 | return Convert.ToBoolean(GetValue("node;IsSelectedPreview")); 68 | } 69 | } 70 | 71 | public string GetValue(string id) 72 | { 73 | ValuePattern pattern = Ae.GetPattern(); 74 | pattern.SetValue(id); 75 | return pattern.Current.Value; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/Model/SimpleSampleTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using MultiSelectTreeView.Test.Model.Helper; 6 | using System.Windows.Automation; 7 | 8 | namespace MultiSelectTreeView.Test.Model 9 | { 10 | class SimpleSampleTree:Tree 11 | { 12 | public SimpleSampleTree(TreeApplication app):base(app.Ae) 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/Model/Tree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using MultiSelectTreeView.Test.Model.Helper; 6 | using System.Windows.Automation; 7 | 8 | namespace MultiSelectTreeView.Test.Model 9 | { 10 | class Tree 11 | { 12 | AutomationElement treeAutomationElement; 13 | Element element1; 14 | Element element11; 15 | Element element12; 16 | Element element13; 17 | Element element14; 18 | Element element15; 19 | 20 | public Tree(AutomationElement treeAutomationElement) 21 | { 22 | this.treeAutomationElement = treeAutomationElement; 23 | } 24 | 25 | public Element Element1 { get { return element1 != null ? element1 : element1 = new Element(treeAutomationElement.FindFirstDescendant(ControlType.TreeItem)); } } 26 | public Element Element11 { get { return element11 != null ? element11 : element11 = new Element(Element1.Ae.FindDescendantByName(ControlType.TreeItem, "element11")); } } 27 | public Element Element12 { get { return element12 != null ? element12 : element12 = new Element(Element1.Ae.FindDescendantByName(ControlType.TreeItem, "element12")); } } 28 | public Element Element13 { get { return element13 != null ? element13 : element13 = new Element(Element1.Ae.FindDescendantByName(ControlType.TreeItem, "element13")); } } 29 | public Element Element14 { get { return element14 != null ? element14 : element14 = new Element(Element1.Ae.FindDescendantByName(ControlType.TreeItem, "element14")); } } 30 | public Element Element15 { get { return element15 != null ? element15 : element15 = new Element(Element1.Ae.FindDescendantByName(ControlType.TreeItem, "element15")); } } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/MouseReactionTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using MultiSelectTreeView.Test.Model.Helper; 7 | using System.Windows.Automation; 8 | using System.Threading; 9 | 10 | namespace MultiSelectTreeView.Test.Model 11 | { 12 | [TestClass] 13 | public class MouseReactionTest 14 | { 15 | [TestMethod] 16 | public void KeyboardLeftRightUpDownArrow() 17 | { 18 | using (TreeApplication app = new TreeApplication("SimpleSample")) 19 | { 20 | SimpleSampleTree sst = new SimpleSampleTree(app); 21 | 22 | Mouse.Click(sst.Element1); 23 | Mouse.ExpandCollapseClick(sst.Element1); 24 | Assert.IsTrue(sst.Element1.IsExpanded, "Element1 not expanded"); 25 | 26 | Mouse.Click(sst.Element11); 27 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 has not focus after down"); 28 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after down"); 29 | } 30 | } 31 | 32 | [TestMethod] 33 | public void KeyboardUpDownWithShiftArrow() 34 | { 35 | using (TreeApplication app = new TreeApplication("SimpleSample")) 36 | { 37 | SimpleSampleTree sst = new SimpleSampleTree(app); 38 | Mouse.ExpandCollapseClick(sst.Element1); 39 | 40 | Mouse.Click(sst.Element11); 41 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 has not focus after down"); 42 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after down"); 43 | 44 | Mouse.ShiftClick(sst.Element12); 45 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after second down"); 46 | Assert.IsTrue(sst.Element12.IsSelected, "Element12 not selected after second down"); 47 | Assert.IsTrue(sst.Element12.IsFocused, "Element12 has not focus after second down"); 48 | 49 | Mouse.Click(sst.Element13); 50 | Assert.IsFalse(sst.Element11.IsSelected, "Element11 selected after second down"); 51 | Assert.IsFalse(sst.Element12.IsSelected, "Element12 selected after second down"); 52 | Assert.IsTrue(sst.Element13.IsSelected, "Element13 not selected after down"); 53 | Assert.IsTrue(sst.Element13.IsFocused, "Element13 has not focus after down"); 54 | 55 | Mouse.ShiftClick(sst.Element11); 56 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 selected after up"); 57 | Assert.IsTrue(sst.Element12.IsSelected, "Element12 selected after up"); 58 | Assert.IsTrue(sst.Element13.IsSelected, "Element13 not selected after up"); 59 | Assert.IsTrue(sst.Element11.IsFocused, "Element13 has not focus after up"); 60 | 61 | 62 | Mouse.ShiftClick(sst.Element14); 63 | Assert.IsTrue(sst.Element13.IsSelected, "Element13 not selected after fourth down"); 64 | Assert.IsTrue(sst.Element14.IsSelected, "Element14 not selected after fourth down"); 65 | Assert.IsTrue(sst.Element14.IsFocused, "Element14 has not focus after fourth down"); 66 | } 67 | } 68 | 69 | [TestMethod] 70 | public void DoubleClick() 71 | { 72 | using (TreeApplication app = new TreeApplication("SimpleSample")) 73 | { 74 | SimpleSampleTree sst = new SimpleSampleTree(app); 75 | 76 | Mouse.DoubleClick(sst.Element1); 77 | Assert.IsTrue(sst.Element1.IsExpanded, "Element1 is not expanded after double click"); 78 | 79 | Mouse.DoubleClick(sst.Element13); 80 | Assert.IsTrue(sst.Element13.IsExpanded, "Element13 is not expanded after double click"); 81 | } 82 | } 83 | 84 | [TestMethod] 85 | public void KeyboardUpDownWithCtrlArrow() 86 | { 87 | using (TreeApplication app = new TreeApplication("SimpleSample")) 88 | { 89 | SimpleSampleTree sst = new SimpleSampleTree(app); 90 | Mouse.ExpandCollapseClick(sst.Element1); 91 | Mouse.Click(sst.Element11); 92 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after down"); 93 | Assert.IsTrue(sst.Element11.IsFocused, "Element11 not has focus after down"); 94 | 95 | Mouse.CtrlClick(sst.Element14); 96 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after ctrlspace"); 97 | Assert.IsTrue(sst.Element14.IsFocused, "Element14 not has focus after ctrlspace"); 98 | Assert.IsTrue(sst.Element14.IsSelected, "Element14 not selected after ctrlspace"); 99 | Assert.IsFalse(sst.Element14.IsSelectedPreview, "Element14 selectedPreview after ctrlspace"); 100 | 101 | Mouse.CtrlClick(sst.Element15); 102 | Assert.IsTrue(sst.Element11.IsSelected, "Element11 not selected after ctrlspace"); 103 | Assert.IsTrue(sst.Element14.IsSelected, "Element14 not selected after ctrlspace"); 104 | Assert.IsFalse(sst.Element14.IsSelectedPreview, "Element14 selectedPreview after ctrlspace"); 105 | Assert.IsTrue(sst.Element15.IsSelected, "Element15 not selected after space"); 106 | Assert.IsTrue(sst.Element15.IsFocused, "Element15 not has focus after space"); 107 | } 108 | } 109 | 110 | #region Public Properties 111 | 112 | public TestContext TestContext { get; set; } 113 | 114 | #endregion 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/MultiSelectTreeView.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {6DA10662-655A-4319-9069-B5DD11FD4EFF} 10 | Library 11 | Properties 12 | MultiSelectTreeView.Test.Model 13 | MultiSelectTreeView.Test 14 | v4.0 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | true 28 | full 29 | false 30 | bin\Debug\ 31 | DEBUG;TRACE 32 | prompt 33 | 4 34 | 35 | 36 | pdbonly 37 | true 38 | bin\Release\ 39 | TRACE 40 | prompt 41 | 4 42 | 43 | 44 | 45 | 46 | 47 | 3.5 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | False 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | {2854814F-EA3C-41D4-AA94-460C4694F430} 79 | MultiSelectTreeView 80 | 81 | 82 | 83 | 90 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/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("MultiSelectTreeView.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("MultiSelectTreeView.Test")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2011")] 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("1a47613b-36d1-4afc-91f6-1877a6006b02")] 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.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /MultiSelectTreeView.Test/SelectionTests.cs: -------------------------------------------------------------------------------- 1 | namespace MultiSelectTreeView.Test.Model 2 | { 3 | #region 4 | 5 | using System.Windows.Automation; 6 | 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | 9 | using MultiSelectTreeView.Test.Model.Helper; 10 | 11 | #endregion 12 | 13 | [TestClass] 14 | [DeploymentItem("W7StyleSample.exe")] 15 | public class SelectionTests 16 | { 17 | #region Constants and Fields 18 | 19 | private const string FileName = "W7StyleSample.exe"; 20 | 21 | private const string ProcessName = "W7StyleSample"; 22 | 23 | #endregion 24 | 25 | #region Public Properties 26 | 27 | public TestContext TestContext { get; set; } 28 | 29 | #endregion 30 | 31 | #region Public Methods 32 | 33 | [TestMethod] 34 | public void SelectElement1() 35 | { 36 | using (TreeApplication app = new TreeApplication("SimpleSample")) 37 | { 38 | SimpleSampleTree sst = new SimpleSampleTree(app); 39 | sst.Element1.Select(); 40 | Assert.IsTrue(sst.Element1.IsSelected); 41 | } 42 | } 43 | 44 | [TestMethod] 45 | public void SelectElement11() 46 | { 47 | using (TreeApplication app = new TreeApplication("SimpleSample")) 48 | { 49 | SimpleSampleTree sst = new SimpleSampleTree(app); 50 | sst.Element1.Expand(); 51 | sst.Element11.Select(); 52 | Assert.IsTrue(sst.Element11.IsSelected); 53 | } 54 | } 55 | 56 | [TestMethod] 57 | public void SelectElement11ByClickOnIt() 58 | { 59 | using (TreeApplication app = new TreeApplication("SimpleSample")) 60 | { 61 | SimpleSampleTree sst = new SimpleSampleTree(app); 62 | sst.Element1.Expand(); 63 | sst.Element11.Select(); 64 | Assert.IsTrue(sst.Element11.IsSelected); 65 | } 66 | } 67 | 68 | 69 | #endregion 70 | } 71 | } -------------------------------------------------------------------------------- /MultiSelectTreeView.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 11.00 2 | # Visual Studio 2010 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiSelectTreeView.Test", "MultiSelectTreeView.Test\MultiSelectTreeView.Test.csproj", "{6DA10662-655A-4319-9069-B5DD11FD4EFF}" 4 | EndProject 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7C333296-263A-440B-9A81-D9C4513812DD}" 6 | ProjectSection(SolutionItems) = preProject 7 | MultiSelectTreeView.vsmdi = MultiSelectTreeView.vsmdi 8 | TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings 9 | EndProjectSection 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiSelectTreeView", "MultiSelectTreeView\MultiSelectTreeView.csproj", "{2854814F-EA3C-41D4-AA94-460C4694F430}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo", "Demo\Demo.csproj", "{D4239AF3-0F7C-4CAB-A70E-98541D120860}" 14 | EndProject 15 | Global 16 | GlobalSection(TestCaseManagementSettings) = postSolution 17 | CategoryFile = MultiSelectTreeView.vsmdi 18 | EndGlobalSection 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {6DA10662-655A-4319-9069-B5DD11FD4EFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {6DA10662-655A-4319-9069-B5DD11FD4EFF}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {6DA10662-655A-4319-9069-B5DD11FD4EFF}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {6DA10662-655A-4319-9069-B5DD11FD4EFF}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {2854814F-EA3C-41D4-AA94-460C4694F430}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {2854814F-EA3C-41D4-AA94-460C4694F430}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {2854814F-EA3C-41D4-AA94-460C4694F430}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {2854814F-EA3C-41D4-AA94-460C4694F430}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {D4239AF3-0F7C-4CAB-A70E-98541D120860}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {D4239AF3-0F7C-4CAB-A70E-98541D120860}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {D4239AF3-0F7C-4CAB-A70E-98541D120860}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {D4239AF3-0F7C-4CAB-A70E-98541D120860}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /MultiSelectTreeView.vsmdi: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Automation/Peers/MultiSelectTreeViewAutomationPeer.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Automation.Provider; 2 | using System.Windows.Controls; 3 | 4 | namespace System.Windows.Automation.Peers 5 | { 6 | public class MultiSelectTreeViewAutomationPeer : ItemsControlAutomationPeer, ISelectionProvider 7 | { 8 | #region Constructor 9 | 10 | public MultiSelectTreeViewAutomationPeer(MultiSelectTreeView owner) 11 | : base(owner) 12 | { 13 | } 14 | 15 | #endregion Constructor 16 | 17 | #region Public properties 18 | 19 | public bool CanSelectMultiple 20 | { 21 | get 22 | { 23 | return false; 24 | } 25 | } 26 | 27 | public bool IsSelectionRequired 28 | { 29 | get 30 | { 31 | return false; 32 | } 33 | } 34 | 35 | #endregion Public properties 36 | 37 | #region Public methods 38 | 39 | public override object GetPattern(PatternInterface patternInterface) 40 | { 41 | if (patternInterface == PatternInterface.Selection) 42 | { 43 | return this; 44 | } 45 | 46 | // if (patternInterface == PatternInterface.Scroll) 47 | // { 48 | // ItemsControl itemsControl = (ItemsControl)Owner; 49 | // if (itemsControl.ScrollHost != null) 50 | // { 51 | // AutomationPeer automationPeer = UIElementAutomationPeer.CreatePeerForElement(itemsControl.ScrollHost); 52 | // if (automationPeer != null && automationPeer is IScrollProvider) 53 | // { 54 | // automationPeer.EventsSource = this; 55 | // return (IScrollProvider)automationPeer; 56 | // } 57 | // } 58 | // } 59 | return base.GetPattern(patternInterface); 60 | } 61 | 62 | #endregion Public methods 63 | 64 | #region Explicit interface methods 65 | 66 | IRawElementProviderSimple[] ISelectionProvider.GetSelection() 67 | { 68 | IRawElementProviderSimple[] array = null; 69 | 70 | // MultiSelectTreeViewItem selectedContainer = ((MultiSelectTreeView) base.Owner).SelectedContainer; 71 | // if (selectedContainer != null) 72 | // { 73 | // AutomationPeer automationPeer = UIElementAutomationPeer.FromElement(selectedContainer); 74 | // if (automationPeer.EventsSource != null) 75 | // { 76 | // automationPeer = automationPeer.EventsSource; 77 | // } 78 | 79 | // if (automationPeer != null) 80 | // { 81 | // array = new[] { this.ProviderFromPeer(automationPeer) }; 82 | // } 83 | // } 84 | 85 | // if (array == null) 86 | // { 87 | // array = new IRawElementProviderSimple[0]; 88 | // } 89 | return array; 90 | } 91 | 92 | #endregion Explicit interface methods 93 | 94 | #region Methods 95 | 96 | /// 97 | /// When overridden in a derived class, creates a new instance of the 98 | /// for a data item in 99 | /// the collection of this 100 | /// . 101 | /// 102 | /// 103 | /// The data item that is associated with this . 104 | /// 105 | /// 106 | /// The new created. 107 | /// 108 | protected override ItemAutomationPeer CreateItemAutomationPeer(object item) 109 | { 110 | return new MultiSelectTreeViewItemDataAutomationPeer(item, this); 111 | } 112 | 113 | protected override AutomationControlType GetAutomationControlTypeCore() 114 | { 115 | return AutomationControlType.Tree; 116 | } 117 | 118 | protected override string GetClassNameCore() 119 | { 120 | return "MultiSelectTreeView"; 121 | } 122 | 123 | #endregion Methods 124 | } 125 | } -------------------------------------------------------------------------------- /MultiSelectTreeView/Automation/Peers/MultiSelectTreeViewItemAutomationPeer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using System.Windows.Automation.Provider; 4 | using System.Windows.Controls; 5 | using System.Windows.Controls.Primitives; 6 | using System.Windows.Media; 7 | 8 | namespace System.Windows.Automation.Peers 9 | { 10 | /// 11 | /// Macht -Typen für 12 | /// UI-Automatisierung verfügbar. 13 | /// 14 | public class MultiSelectTreeViewItemAutomationPeer : 15 | ItemsControlAutomationPeer, 16 | IExpandCollapseProvider, 17 | ISelectionItemProvider, 18 | IScrollItemProvider, 19 | IValueProvider, 20 | IInvokeProvider 21 | { 22 | #region Constructor 23 | 24 | /// 25 | /// Initializes a new instance of the 26 | /// class. 27 | /// 28 | /// 29 | /// Das , das diesem 30 | /// 31 | /// zugeordnet ist. 32 | /// 33 | public MultiSelectTreeViewItemAutomationPeer(MultiSelectTreeViewItem owner) 34 | : base(owner) 35 | { 36 | } 37 | 38 | #endregion Constructor 39 | 40 | protected override Rect GetBoundingRectangleCore() 41 | { 42 | var treeViewItem = (MultiSelectTreeViewItem) Owner; 43 | var contentPresenter = GetContentPresenter(treeViewItem); 44 | if (contentPresenter != null) 45 | { 46 | Vector offset = VisualTreeHelper.GetOffset(contentPresenter); 47 | Point p = new Point(offset.X, offset.Y); 48 | p = contentPresenter.PointToScreen(p); 49 | return new Rect(p.X, p.Y, contentPresenter.ActualWidth, contentPresenter.ActualHeight); 50 | } 51 | 52 | return base.GetBoundingRectangleCore(); 53 | } 54 | 55 | protected override Point GetClickablePointCore() 56 | { 57 | var treeViewItem = (MultiSelectTreeViewItem) Owner; 58 | var contentPresenter = GetContentPresenter(treeViewItem); 59 | if (contentPresenter != null) 60 | { 61 | Vector offset = VisualTreeHelper.GetOffset(contentPresenter); 62 | Point p = new Point(offset.X, offset.Y); 63 | p = contentPresenter.PointToScreen(p); 64 | return p; 65 | } 66 | 67 | return base.GetClickablePointCore(); 68 | } 69 | 70 | private static ContentPresenter GetContentPresenter(MultiSelectTreeViewItem treeViewItem) 71 | { 72 | var contentPresenter = treeViewItem.Template.FindName("PART_Header", treeViewItem) as ContentPresenter; 73 | return contentPresenter; 74 | } 75 | 76 | /// 77 | /// Overridden because original wpf tree does show the expander button and the contents of the 78 | /// header as children, too. That was requested by the users. 79 | /// 80 | /// Returns a list of children. 81 | protected override List GetChildrenCore() 82 | { 83 | //System.Diagnostics.Trace.WriteLine("MultiSelectTreeViewItemAutomationPeer.GetChildrenCore()"); 84 | MultiSelectTreeViewItem owner = (MultiSelectTreeViewItem) Owner; 85 | 86 | List children = new List(); 87 | var button = owner.Template.FindName("Expander", owner) as ToggleButton; 88 | AddAutomationPeer(children, button); 89 | //System.Diagnostics.Trace.WriteLine("- Adding ToggleButton, " + (button == null ? "IS" : "is NOT") + " null, now " + children.Count + " items"); 90 | 91 | var contentPresenter = GetContentPresenter(owner); 92 | 93 | if (contentPresenter != null) 94 | { 95 | int childrenCount = VisualTreeHelper.GetChildrenCount(contentPresenter); 96 | for (int i = 0; i < childrenCount; i++) 97 | { 98 | var child = VisualTreeHelper.GetChild(contentPresenter, i) as UIElement; 99 | AddAutomationPeer(children, child); 100 | //System.Diagnostics.Trace.WriteLine("- Adding child UIElement, " + (child == null ? "IS" : "is NOT") + " null, now " + children.Count + " items"); 101 | } 102 | } 103 | 104 | ItemCollection items = owner.Items; 105 | for (int i = 0; i < items.Count; i++) 106 | { 107 | MultiSelectTreeViewItem treeViewItem = owner.ItemContainerGenerator.ContainerFromIndex(i) as MultiSelectTreeViewItem; 108 | AddAutomationPeer(children, treeViewItem); 109 | //System.Diagnostics.Trace.WriteLine("- Adding MultiSelectTreeViewItem, " + (treeViewItem == null ? "IS" : "is NOT") + " null, now " + children.Count + " items"); 110 | } 111 | 112 | if (children.Count > 0) 113 | { 114 | //System.Diagnostics.Trace.WriteLine("MultiSelectTreeViewItemAutomationPeer.GetChildrenCore(): returning " + children.Count + " children"); 115 | //for (int i = 0; i < children.Count; i++) 116 | //{ 117 | // System.Diagnostics.Trace.WriteLine("- Item " + i + " " + (children[i] == null ? "IS" : "is NOT") + " null"); 118 | //} 119 | return children; 120 | } 121 | 122 | //System.Diagnostics.Trace.WriteLine("MultiSelectTreeViewItemAutomationPeer.GetChildrenCore(): returning null"); 123 | return null; 124 | } 125 | 126 | private static void AddAutomationPeer(List children, UIElement child) 127 | { 128 | if (child != null) 129 | { 130 | AutomationPeer peer = FromElement(child); 131 | if (peer == null) 132 | { 133 | peer = CreatePeerForElement(child); 134 | } 135 | 136 | if (peer != null) 137 | { 138 | // In the array that GetChildrenCore returns, which is used by AutomationPeer.EnsureChildren, 139 | // no null entries are allowed or a NullReferenceException will be thrown from the guts of WPF. 140 | // This has reproducibly been observed null on certain systems so the null check was added. 141 | // This may mean that some child controls are missing for automation, but at least the 142 | // application doesn't crash in normal usage. 143 | children.Add(peer); 144 | } 145 | } 146 | } 147 | 148 | #region Public properties 149 | 150 | public ExpandCollapseState ExpandCollapseState 151 | { 152 | get 153 | { 154 | MultiSelectTreeViewItem treeViewItem = (MultiSelectTreeViewItem) Owner; 155 | if (!treeViewItem.HasItems) 156 | { 157 | return ExpandCollapseState.LeafNode; 158 | } 159 | 160 | if (!treeViewItem.IsExpanded) 161 | { 162 | return ExpandCollapseState.Collapsed; 163 | } 164 | 165 | return ExpandCollapseState.Expanded; 166 | } 167 | } 168 | 169 | #endregion Public properties 170 | 171 | #region Explicit interface properties 172 | 173 | bool ISelectionItemProvider.IsSelected 174 | { 175 | get 176 | { 177 | return ((MultiSelectTreeViewItem) Owner).IsSelected; 178 | } 179 | } 180 | 181 | IRawElementProviderSimple ISelectionItemProvider.SelectionContainer 182 | { 183 | get 184 | { 185 | ItemsControl parentItemsControl = ((MultiSelectTreeViewItem) Owner).ParentTreeView; 186 | if (parentItemsControl != null) 187 | { 188 | AutomationPeer automationPeer = FromElement(parentItemsControl); 189 | if (automationPeer != null) 190 | { 191 | return ProviderFromPeer(automationPeer); 192 | } 193 | } 194 | 195 | return null; 196 | } 197 | } 198 | 199 | #endregion Explicit interface properties 200 | 201 | #region Public methods 202 | 203 | public void Collapse() 204 | { 205 | if (!IsEnabled()) 206 | { 207 | throw new ElementNotEnabledException(); 208 | } 209 | 210 | MultiSelectTreeViewItem treeViewItem = (MultiSelectTreeViewItem) Owner; 211 | if (!treeViewItem.HasItems) 212 | { 213 | throw new InvalidOperationException("Cannot collapse because item has no children."); 214 | } 215 | 216 | treeViewItem.IsExpanded = false; 217 | } 218 | 219 | public void Expand() 220 | { 221 | if (!IsEnabled()) 222 | { 223 | throw new ElementNotEnabledException(); 224 | } 225 | 226 | MultiSelectTreeViewItem treeViewItem = (MultiSelectTreeViewItem) Owner; 227 | if (!treeViewItem.HasItems) 228 | { 229 | throw new InvalidOperationException("Cannot expand because item has no children."); 230 | } 231 | 232 | treeViewItem.IsExpanded = true; 233 | } 234 | 235 | public override object GetPattern(PatternInterface patternInterface) 236 | { 237 | if (patternInterface == PatternInterface.ExpandCollapse) 238 | { 239 | return this; 240 | } 241 | 242 | if (patternInterface == PatternInterface.SelectionItem) 243 | { 244 | return this; 245 | } 246 | 247 | if (patternInterface == PatternInterface.ScrollItem) 248 | { 249 | return this; 250 | } 251 | 252 | if (patternInterface == PatternInterface.Value) 253 | { 254 | return this; 255 | } 256 | 257 | return base.GetPattern(patternInterface); 258 | } 259 | 260 | #endregion Public methods 261 | 262 | #region Explicit interface methods 263 | 264 | void IScrollItemProvider.ScrollIntoView() 265 | { 266 | ((MultiSelectTreeViewItem) Owner).BringIntoView(); 267 | } 268 | 269 | void ISelectionItemProvider.AddToSelection() 270 | { 271 | throw new NotImplementedException(); 272 | } 273 | 274 | void ISelectionItemProvider.RemoveFromSelection() 275 | { 276 | throw new NotImplementedException(); 277 | } 278 | 279 | void ISelectionItemProvider.Select() 280 | { 281 | ((MultiSelectTreeViewItem) Owner).ParentTreeView.Selection.SelectCore((MultiSelectTreeViewItem) Owner); 282 | } 283 | 284 | #endregion Explicit interface methods 285 | 286 | #region Methods 287 | 288 | protected override ItemAutomationPeer CreateItemAutomationPeer(object item) 289 | { 290 | return new MultiSelectTreeViewItemDataAutomationPeer(item, this); 291 | } 292 | 293 | protected override AutomationControlType GetAutomationControlTypeCore() 294 | { 295 | return AutomationControlType.TreeItem; 296 | } 297 | 298 | protected override string GetClassNameCore() 299 | { 300 | return "MultiSelectTreeViewItem"; 301 | } 302 | 303 | #endregion Methods 304 | 305 | #region IValueProvider members 306 | 307 | public bool IsReadOnly 308 | { 309 | get { return false; } 310 | } 311 | 312 | string requestedValue; 313 | 314 | public void SetValue(string value) 315 | { 316 | try 317 | { 318 | if (String.IsNullOrWhiteSpace(value)) return; 319 | 320 | string[] ids = value.Split(new[] { ';' }); 321 | 322 | object obj; 323 | if (ids.Length > 0 && ids[0] == "Context") 324 | { 325 | MultiSelectTreeViewItem treeViewItem = (MultiSelectTreeViewItem) Owner; 326 | obj = treeViewItem.DataContext; 327 | } 328 | else 329 | { 330 | obj = Owner; 331 | } 332 | 333 | if (ids.Length < 2) 334 | { 335 | requestedValue = obj.ToString(); 336 | } 337 | else 338 | { 339 | Type type = obj.GetType(); 340 | PropertyInfo pi = type.GetProperty(ids[1]); 341 | requestedValue = pi.GetValue(obj, null).ToString(); 342 | } 343 | } 344 | catch (Exception ex) 345 | { 346 | requestedValue = ex.ToString(); 347 | } 348 | } 349 | 350 | public string Value 351 | { 352 | get 353 | { 354 | if (requestedValue == null) 355 | { 356 | MultiSelectTreeViewItem treeViewItem = (MultiSelectTreeViewItem) Owner; 357 | return treeViewItem.DataContext.ToString(); 358 | } 359 | 360 | return requestedValue; 361 | } 362 | } 363 | 364 | #endregion IValueProvider members 365 | 366 | #region IInvokeProvider members 367 | 368 | public void Invoke() 369 | { 370 | MultiSelectTreeViewItem treeViewItem = (MultiSelectTreeViewItem) Owner; 371 | treeViewItem.InvokeMouseDown(); 372 | } 373 | 374 | #endregion IInvokeProvider members 375 | } 376 | } -------------------------------------------------------------------------------- /MultiSelectTreeView/Automation/Peers/MultiSelectTreeViewItemDataAutomationPeer.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Automation.Provider; 2 | using System.Windows.Controls; 3 | 4 | namespace System.Windows.Automation.Peers 5 | { 6 | public class MultiSelectTreeViewItemDataAutomationPeer : 7 | ItemAutomationPeer, 8 | ISelectionItemProvider, 9 | IScrollItemProvider, 10 | IExpandCollapseProvider, 11 | IValueProvider 12 | { 13 | #region Constructor 14 | 15 | public MultiSelectTreeViewItemDataAutomationPeer(object item, ItemsControlAutomationPeer itemsControlAutomationPeer) 16 | : base(item, itemsControlAutomationPeer) 17 | { 18 | } 19 | 20 | #endregion Constructor 21 | 22 | #region Explicit interface properties 23 | 24 | ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState 25 | { 26 | get 27 | { 28 | return ItemPeer.ExpandCollapseState; 29 | } 30 | } 31 | 32 | bool ISelectionItemProvider.IsSelected 33 | { 34 | get 35 | { 36 | return ((ISelectionItemProvider) ItemPeer).IsSelected; 37 | } 38 | } 39 | 40 | IRawElementProviderSimple ISelectionItemProvider.SelectionContainer 41 | { 42 | get 43 | { 44 | // TreeViewItemAutomationPeer treeViewItemAutomationPeer = GetWrapperPeer() as TreeViewItemAutomationPeer; 45 | // if (treeViewItemAutomationPeer != null) 46 | // { 47 | // ISelectionItemProvider selectionItemProvider = treeViewItemAutomationPeer; 48 | // return selectionItemProvider.SelectionContainer; 49 | // } 50 | 51 | // this.ThrowElementNotAvailableException(); 52 | return null; 53 | } 54 | } 55 | 56 | #endregion Explicit interface properties 57 | 58 | #region Properties 59 | 60 | private MultiSelectTreeViewItemAutomationPeer ItemPeer 61 | { 62 | get 63 | { 64 | AutomationPeer automationPeer = null; 65 | UIElement wrapper = GetWrapper(); 66 | if (wrapper != null) 67 | { 68 | automationPeer = UIElementAutomationPeer.CreatePeerForElement(wrapper); 69 | if (automationPeer == null) 70 | { 71 | if (wrapper is FrameworkElement) 72 | { 73 | automationPeer = new FrameworkElementAutomationPeer((FrameworkElement) wrapper); 74 | } 75 | else 76 | { 77 | automationPeer = new UIElementAutomationPeer(wrapper); 78 | } 79 | } 80 | } 81 | 82 | var treeViewItemAutomationPeer = automationPeer as MultiSelectTreeViewItemAutomationPeer; 83 | 84 | if (treeViewItemAutomationPeer == null) 85 | { 86 | throw new InvalidOperationException("Could not find parent automation peer."); 87 | } 88 | 89 | return treeViewItemAutomationPeer; 90 | } 91 | } 92 | 93 | #endregion Properties 94 | 95 | #region Public methods 96 | 97 | public override object GetPattern(PatternInterface patternInterface) 98 | { 99 | if (patternInterface == PatternInterface.ExpandCollapse) 100 | { 101 | return this; 102 | } 103 | 104 | if (patternInterface == PatternInterface.SelectionItem) 105 | { 106 | return this; 107 | } 108 | 109 | if (patternInterface == PatternInterface.ScrollItem) 110 | { 111 | return this; 112 | } 113 | 114 | if (patternInterface == PatternInterface.Value) 115 | { 116 | return this; 117 | } 118 | 119 | if (patternInterface == PatternInterface.ItemContainer 120 | || patternInterface == PatternInterface.SynchronizedInput) 121 | { 122 | return ItemPeer; 123 | } 124 | 125 | return base.GetPattern(patternInterface); 126 | } 127 | 128 | #endregion Public methods 129 | 130 | #region Explicit interface methods 131 | 132 | void IExpandCollapseProvider.Collapse() 133 | { 134 | ItemPeer.Collapse(); 135 | } 136 | 137 | void IExpandCollapseProvider.Expand() 138 | { 139 | ItemPeer.Expand(); 140 | } 141 | 142 | void IScrollItemProvider.ScrollIntoView() 143 | { 144 | ((IScrollItemProvider) ItemPeer).ScrollIntoView(); 145 | } 146 | 147 | void ISelectionItemProvider.AddToSelection() 148 | { 149 | ((ISelectionItemProvider) ItemPeer).AddToSelection(); 150 | } 151 | 152 | void ISelectionItemProvider.RemoveFromSelection() 153 | { 154 | ((ISelectionItemProvider) ItemPeer).RemoveFromSelection(); 155 | } 156 | 157 | void ISelectionItemProvider.Select() 158 | { 159 | ((ISelectionItemProvider) ItemPeer).Select(); 160 | } 161 | 162 | #endregion Explicit interface methods 163 | 164 | #region Methods 165 | 166 | protected override AutomationControlType GetAutomationControlTypeCore() 167 | { 168 | return AutomationControlType.TreeItem; 169 | } 170 | 171 | protected override string GetClassNameCore() 172 | { 173 | return "TreeViewItem"; 174 | } 175 | 176 | private UIElement GetWrapper() 177 | { 178 | UIElement result = null; 179 | ItemsControlAutomationPeer itemsControlAutomationPeer = ItemsControlAutomationPeer; 180 | if (itemsControlAutomationPeer != null) 181 | { 182 | ItemsControl itemsControl = (ItemsControl) itemsControlAutomationPeer.Owner; 183 | if (itemsControl != null) 184 | { 185 | result = itemsControl.ItemContainerGenerator.ContainerFromItem(Item) as UIElement; 186 | } 187 | } 188 | 189 | return result; 190 | } 191 | 192 | #endregion Methods 193 | 194 | #region IValueProvider members 195 | 196 | bool IValueProvider.IsReadOnly 197 | { 198 | get { return ((IValueProvider) ItemPeer).IsReadOnly; } 199 | } 200 | 201 | void IValueProvider.SetValue(string value) 202 | { 203 | ((IValueProvider) ItemPeer).SetValue(value); 204 | } 205 | 206 | string IValueProvider.Value 207 | { 208 | get { return ((IValueProvider) ItemPeer).Value; } 209 | } 210 | 211 | #endregion IValueProvider members 212 | } 213 | } -------------------------------------------------------------------------------- /MultiSelectTreeView/Controls/BorderSelectionLogic.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Windows.Input; 4 | 5 | namespace System.Windows.Controls 6 | { 7 | internal class BorderSelectionLogic : IDisposable 8 | { 9 | #region Private fields 10 | 11 | private MultiSelectTreeView treeView; 12 | private readonly Border border; 13 | private readonly ScrollViewer scrollViewer; 14 | private readonly ItemsPresenter content; 15 | private readonly IEnumerable items; 16 | 17 | private bool isFirstMove; 18 | private bool mouseDown; 19 | private Point startPoint; 20 | private DateTime lastScrollTime; 21 | private HashSet initialSelection; 22 | 23 | #endregion Private fields 24 | 25 | #region Constructor 26 | 27 | public BorderSelectionLogic(MultiSelectTreeView treeView, Border selectionBorder, ScrollViewer scrollViewer, ItemsPresenter content, IEnumerable items) 28 | { 29 | if (treeView == null) 30 | { 31 | throw new ArgumentNullException("treeView"); 32 | } 33 | if (selectionBorder == null) 34 | { 35 | throw new ArgumentNullException("selectionBorder"); 36 | } 37 | if (scrollViewer == null) 38 | { 39 | throw new ArgumentNullException("scrollViewer"); 40 | } 41 | if (content == null) 42 | { 43 | throw new ArgumentNullException("content"); 44 | } 45 | if (items == null) 46 | { 47 | throw new ArgumentNullException("items"); 48 | } 49 | 50 | this.treeView = treeView; 51 | this.border = selectionBorder; 52 | this.scrollViewer = scrollViewer; 53 | this.content = content; 54 | this.items = items; 55 | 56 | treeView.MouseDown += OnMouseDown; 57 | treeView.MouseMove += OnMouseMove; 58 | treeView.MouseUp += OnMouseUp; 59 | treeView.KeyDown += OnKeyDown; 60 | treeView.KeyUp += OnKeyUp; 61 | } 62 | 63 | #endregion Constructor 64 | 65 | #region Public methods 66 | 67 | public void Dispose() 68 | { 69 | if (treeView != null) 70 | { 71 | treeView.MouseDown -= OnMouseDown; 72 | treeView.MouseMove -= OnMouseMove; 73 | treeView.MouseUp -= OnMouseUp; 74 | treeView.KeyDown -= OnKeyDown; 75 | treeView.KeyUp -= OnKeyUp; 76 | treeView = null; 77 | } 78 | GC.SuppressFinalize(this); 79 | } 80 | 81 | #endregion Public methods 82 | 83 | #region Methods 84 | 85 | private void OnMouseDown(object sender, MouseButtonEventArgs e) 86 | { 87 | mouseDown = true; 88 | startPoint = Mouse.GetPosition(content); 89 | 90 | // Debug.WriteLine("Initialize drwawing"); 91 | isFirstMove = true; 92 | // Capture the mouse right now so that the MouseUp event will not be missed 93 | Mouse.Capture(treeView); 94 | 95 | initialSelection = new HashSet(treeView.SelectedItems.Cast()); 96 | } 97 | 98 | private void OnMouseMove(object sender, MouseEventArgs e) 99 | { 100 | if (mouseDown) 101 | { 102 | if (DateTime.UtcNow > lastScrollTime.AddMilliseconds(100)) 103 | { 104 | Point currentPointWin = Mouse.GetPosition(scrollViewer); 105 | if (currentPointWin.Y < 16) 106 | { 107 | scrollViewer.LineUp(); 108 | scrollViewer.UpdateLayout(); 109 | lastScrollTime = DateTime.UtcNow; 110 | } 111 | if (currentPointWin.Y > scrollViewer.ActualHeight - 16) 112 | { 113 | scrollViewer.LineDown(); 114 | scrollViewer.UpdateLayout(); 115 | lastScrollTime = DateTime.UtcNow; 116 | } 117 | } 118 | 119 | Point currentPoint = Mouse.GetPosition(content); 120 | double width = currentPoint.X - startPoint.X + 1; 121 | double height = currentPoint.Y - startPoint.Y + 1; 122 | double left = startPoint.X; 123 | double top = startPoint.Y; 124 | 125 | if (isFirstMove) 126 | { 127 | if (Math.Abs(width) <= SystemParameters.MinimumHorizontalDragDistance && 128 | Math.Abs(height) <= SystemParameters.MinimumVerticalDragDistance) 129 | { 130 | return; 131 | } 132 | 133 | isFirstMove = false; 134 | if (!SelectionMultiple.IsControlKeyDown) 135 | { 136 | if (!treeView.ClearSelectionByRectangle()) 137 | { 138 | EndAction(); 139 | return; 140 | } 141 | } 142 | } 143 | 144 | // Debug.WriteLine(string.Format("Drawing: {0};{1};{2};{3}",startPoint.X,startPoint.Y,width,height)); 145 | if (width < 1) 146 | { 147 | width = Math.Abs(width - 1) + 1; 148 | left = startPoint.X - width + 1; 149 | } 150 | 151 | if (height < 1) 152 | { 153 | height = Math.Abs(height - 1) + 1; 154 | top = startPoint.Y - height + 1; 155 | } 156 | 157 | border.Width = width; 158 | Canvas.SetLeft(border, left); 159 | border.Height = height; 160 | Canvas.SetTop(border, top); 161 | 162 | border.Visibility = Visibility.Visible; 163 | 164 | double right = left + width - 1; 165 | double bottom = top + height - 1; 166 | 167 | // Debug.WriteLine(string.Format("left:{1};right:{2};top:{3};bottom:{4}", null, left, right, top, bottom)); 168 | SelectionMultiple selection = (SelectionMultiple) treeView.Selection; 169 | bool foundFocusItem = false; 170 | foreach (var item in items) 171 | { 172 | FrameworkElement itemContent = (FrameworkElement) item.Template.FindName("headerBorder", item); 173 | Point p = itemContent.TransformToAncestor(content).Transform(new Point()); 174 | double itemLeft = p.X; 175 | double itemRight = p.X + itemContent.ActualWidth - 1; 176 | double itemTop = p.Y; 177 | double itemBottom = p.Y + itemContent.ActualHeight - 1; 178 | 179 | // Debug.WriteLine(string.Format("element:{0};itemleft:{1};itemright:{2};itemtop:{3};itembottom:{4}",item.DataContext,itemLeft,itemRight,itemTop,itemBottom)); 180 | 181 | // Compute the current input states for determining the new selection state of the item 182 | bool intersect = !(itemLeft > right || itemRight < left || itemTop > bottom || itemBottom < top); 183 | bool initialSelected = initialSelection != null && initialSelection.Contains(item.DataContext); 184 | bool ctrl = SelectionMultiple.IsControlKeyDown; 185 | 186 | // Decision matrix: 187 | // If the Ctrl key is pressed, each intersected item will be toggled from its initial selection. 188 | // Without the Ctrl key, each intersected item is selected, others are deselected. 189 | // 190 | // newSelected 191 | // ─────────┬─────────────────────── 192 | // │ intersect 193 | // │ 0 │ 1 194 | // ├───────────┴─────────── 195 | // │ initial 196 | // │ 0 │ 1 │ 0 │ 1 197 | // ─────────┼─────┼─────┼─────┼───── 198 | // ctrl 0 │ 0 │ 0 │ 1 │ 1 = intersect 199 | // ─────────┼─────┼─────┼─────┼───── 200 | // 1 │ 0 │ 1 │ 1 │ 0 = intersect XOR initial 201 | // 202 | bool newSelected = intersect ^ (initialSelected && ctrl); 203 | 204 | // The new selection state for this item has been determined. Apply it. 205 | if (newSelected) 206 | { 207 | // The item shall be selected 208 | if (!treeView.SelectedItems.Contains(item.DataContext)) 209 | { 210 | // The item is not currently selected. Try to select it. 211 | if (!selection.SelectByRectangle(item)) 212 | { 213 | if (selection.LastCancelAll) 214 | { 215 | EndAction(); 216 | return; 217 | } 218 | } 219 | } 220 | } 221 | else 222 | { 223 | // The item shall be deselected 224 | if (treeView.SelectedItems.Contains(item.DataContext)) 225 | { 226 | // The item is currently selected. Try to deselect it. 227 | if (!selection.DeselectByRectangle(item)) 228 | { 229 | if (selection.LastCancelAll) 230 | { 231 | EndAction(); 232 | return; 233 | } 234 | } 235 | } 236 | } 237 | 238 | // Always focus and bring into view the item under the mouse cursor 239 | if (!foundFocusItem && 240 | currentPoint.X >= itemLeft && currentPoint.X <= itemRight && 241 | currentPoint.Y >= itemTop && currentPoint.Y <= itemBottom) 242 | { 243 | FocusHelper.Focus(item, true); 244 | scrollViewer.UpdateLayout(); 245 | foundFocusItem = true; 246 | } 247 | } 248 | 249 | if (e != null) 250 | { 251 | e.Handled = true; 252 | } 253 | } 254 | } 255 | 256 | private void OnMouseUp(object sender, MouseButtonEventArgs e) 257 | { 258 | EndAction(); 259 | 260 | // Clear selection if this was a non-ctrl click outside of any item (i.e. in the background) 261 | Point currentPoint = e.GetPosition(content); 262 | double width = currentPoint.X - startPoint.X + 1; 263 | double height = currentPoint.Y - startPoint.Y + 1; 264 | if (Math.Abs(width) <= SystemParameters.MinimumHorizontalDragDistance && 265 | Math.Abs(height) <= SystemParameters.MinimumVerticalDragDistance && 266 | !SelectionMultiple.IsControlKeyDown) 267 | { 268 | treeView.ClearSelection(); 269 | } 270 | } 271 | 272 | private void OnKeyDown(object sender, KeyEventArgs e) 273 | { 274 | // The mouse move handler reads the Ctrl key so is dependent on it. 275 | // If the key state has changed, the selection needs to be updated. 276 | OnMouseMove(null, null); 277 | } 278 | 279 | private void OnKeyUp(object sender, KeyEventArgs e) 280 | { 281 | // The mouse move handler reads the Ctrl key so is dependent on it. 282 | // If the key state has changed, the selection needs to be updated. 283 | OnMouseMove(null, null); 284 | } 285 | 286 | private void EndAction() 287 | { 288 | Mouse.Capture(null); 289 | mouseDown = false; 290 | border.Visibility = Visibility.Collapsed; 291 | initialSelection = null; 292 | 293 | // Debug.WriteLine("End drawing"); 294 | } 295 | 296 | #endregion Methods 297 | } 298 | } -------------------------------------------------------------------------------- /MultiSelectTreeView/Controls/EditTextBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Data; 2 | using System.Windows.Input; 3 | 4 | namespace System.Windows.Controls 5 | { 6 | /// 7 | /// Text box which focuses itself on load and selects all text in it. 8 | /// 9 | public class EditTextBox : TextBox 10 | { 11 | #region Private fields 12 | 13 | private string startText; 14 | 15 | #endregion Private fields 16 | 17 | #region Constructor 18 | 19 | static EditTextBox() 20 | { 21 | DefaultStyleKeyProperty.OverrideMetadata(typeof(EditTextBox), new FrameworkPropertyMetadata(typeof(EditTextBox))); 22 | } 23 | 24 | public EditTextBox() 25 | { 26 | Loaded += OnTreeViewEditTextBoxLoaded; 27 | } 28 | 29 | #endregion Constructor 30 | 31 | #region Methods 32 | 33 | protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) 34 | { 35 | base.OnGotKeyboardFocus(e); 36 | startText = Text; 37 | SelectAll(); 38 | } 39 | 40 | protected override void OnKeyDown(KeyEventArgs e) 41 | { 42 | base.OnKeyDown(e); 43 | if (!e.Handled) 44 | { 45 | Key key = e.Key; 46 | switch (key) 47 | { 48 | case Key.Escape: 49 | Text = startText; 50 | break; 51 | } 52 | } 53 | } 54 | 55 | private void OnTreeViewEditTextBoxLoaded(object sender, RoutedEventArgs e) 56 | { 57 | BindingExpression be = GetBindingExpression(TextProperty); 58 | if (be != null) be.UpdateTarget(); 59 | FocusHelper.Focus(this); 60 | } 61 | 62 | #endregion 63 | } 64 | } -------------------------------------------------------------------------------- /MultiSelectTreeView/Controls/FocusHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Windows.Threading; 3 | 4 | namespace System.Windows.Controls 5 | { 6 | /// 7 | /// Helper methods to focus. 8 | /// 9 | public static class FocusHelper 10 | { 11 | #region Public methods 12 | 13 | public static void Focus(EditTextBox element) 14 | { 15 | //System.Diagnostics.Debug.WriteLine("Focus textbox with helper:" + element.Text); 16 | FocusCore(element); 17 | element.BringIntoView(); 18 | } 19 | 20 | public static void Focus(MultiSelectTreeViewItem element, bool bringIntoView = false) 21 | { 22 | //System.Diagnostics.Debug.WriteLine("FocusHelper focusing " + (bringIntoView ? "[into view] " : "") + element.DataContext); 23 | FocusCore(element); 24 | 25 | if (bringIntoView) 26 | { 27 | FrameworkElement itemContent = (FrameworkElement) element.Template.FindName("headerBorder", element); 28 | if (itemContent != null) // May not be rendered yet... 29 | { 30 | itemContent.BringIntoView(); 31 | } 32 | } 33 | } 34 | 35 | public static void Focus(MultiSelectTreeView element) 36 | { 37 | //System.Diagnostics.Debug.WriteLine("Focus Tree with helper"); 38 | FocusCore(element); 39 | element.BringIntoView(); 40 | } 41 | 42 | private static void FocusCore(FrameworkElement element) 43 | { 44 | //System.Diagnostics.Debug.WriteLine("Focusing element " + element.ToString()); 45 | //System.Diagnostics.Debug.WriteLine(Environment.StackTrace); 46 | if (!element.Focus()) 47 | { 48 | //System.Diagnostics.Debug.WriteLine("- Element could not be focused, invoking in dispatcher thread"); 49 | element.Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(() => element.Focus())); 50 | } 51 | 52 | #if DEBUG 53 | // no good idea, seems to block sometimes 54 | int i = 0; 55 | while (i < 5) 56 | { 57 | if (element.IsFocused) 58 | { 59 | //if (i > 0) 60 | // System.Diagnostics.Debug.WriteLine("- Element is focused now in round " + i + ", leaving"); 61 | return; 62 | } 63 | Thread.Sleep(20); 64 | i++; 65 | } 66 | //if (i >= 5) 67 | //{ 68 | // System.Diagnostics.Debug.WriteLine("- Element is not focused after 500 ms, giving up"); 69 | //} 70 | #endif 71 | } 72 | 73 | #endregion Public methods 74 | } 75 | } -------------------------------------------------------------------------------- /MultiSelectTreeView/Controls/ISelectionStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace System.Windows.Controls 2 | { 3 | internal interface ISelectionStrategy : IDisposable 4 | { 5 | event EventHandler PreviewSelectionChanged; 6 | 7 | void ApplyTemplate(); 8 | bool SelectCore(MultiSelectTreeViewItem owner); 9 | bool Deselect(MultiSelectTreeViewItem item, bool bringIntoView = false); 10 | bool SelectPreviousFromKey(); 11 | bool SelectNextFromKey(); 12 | bool SelectFirstFromKey(); 13 | bool SelectLastFromKey(); 14 | bool SelectPageUpFromKey(); 15 | bool SelectPageDownFromKey(); 16 | bool SelectAllFromKey(); 17 | bool SelectParentFromKey(); 18 | bool SelectCurrentBySpace(); 19 | bool Select(MultiSelectTreeViewItem treeViewItem); 20 | } 21 | 22 | public class PreviewSelectionChangedEventArgs : EventArgs 23 | { 24 | /// 25 | /// Gets a value indicating whether the item was selected or deselected. 26 | /// 27 | public bool Selecting { get; private set; } 28 | /// 29 | /// Gets the item that is being selected or deselected. 30 | /// 31 | public object Item { get; private set; } 32 | /// 33 | /// Gets or sets a value indicating whether the selection change of this item shall be 34 | /// cancelled. 35 | /// 36 | public bool CancelThis { get; set; } 37 | /// 38 | /// Gets or sets a value indicating whether the selection change of this item and all other 39 | /// affected items shall be cancelled. 40 | /// 41 | public bool CancelAll { get; set; } 42 | 43 | /// 44 | /// Gets a value indicating whether any of the Cancel flags is set. 45 | /// 46 | public bool CancelAny { get { return CancelThis || CancelAll; } } 47 | 48 | public PreviewSelectionChangedEventArgs(bool selecting, object item) 49 | { 50 | #if DEBUG 51 | // Make sure we don't confuse MultiSelectTreeViewItems and their DataContexts while development 52 | if (item is MultiSelectTreeViewItem) 53 | throw new ArgumentException("The selection preview event was passed a MultiSelectTreeViewItem instance. Only their DataContext instances must be used here!"); 54 | #endif 55 | 56 | Selecting = selecting; 57 | Item = item; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Controls/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace System.Windows.Controls 4 | { 5 | internal static class ListExtensions 6 | { 7 | internal static object Last(this IList list) 8 | { 9 | if (list.Count < 1) 10 | { 11 | return null; 12 | } 13 | return list[list.Count - 1]; 14 | } 15 | 16 | internal static object First(this IList list) 17 | { 18 | if (list.Count < 1) 19 | { 20 | return null; 21 | } 22 | return list[0]; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Controls/SelectionMultiple.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows.Input; 5 | using System.Windows.Media; 6 | 7 | namespace System.Windows.Controls 8 | { 9 | /// 10 | /// Implements the logic for the multiple selection strategy. 11 | /// 12 | public class SelectionMultiple : ISelectionStrategy 13 | { 14 | public event EventHandler PreviewSelectionChanged; 15 | 16 | private readonly MultiSelectTreeView treeView; 17 | private BorderSelectionLogic borderSelectionLogic; 18 | private object lastShiftRoot; 19 | 20 | public SelectionMultiple(MultiSelectTreeView treeView) 21 | { 22 | this.treeView = treeView; 23 | } 24 | 25 | #region Properties 26 | 27 | public bool LastCancelAll { get; private set; } 28 | 29 | internal static bool IsControlKeyDown 30 | { 31 | get 32 | { 33 | return (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control; 34 | } 35 | } 36 | 37 | private static bool IsShiftKeyDown 38 | { 39 | get 40 | { 41 | return (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift; 42 | } 43 | } 44 | 45 | #endregion 46 | 47 | public void InvalidateLastShiftRoot(object item) 48 | { 49 | if (lastShiftRoot == item) 50 | { 51 | lastShiftRoot = null; 52 | } 53 | } 54 | 55 | public void ApplyTemplate() 56 | { 57 | borderSelectionLogic = new BorderSelectionLogic( 58 | treeView, 59 | treeView.Template.FindName("selectionBorder", treeView) as Border, 60 | treeView.Template.FindName("scrollViewer", treeView) as ScrollViewer, 61 | treeView.Template.FindName("content", treeView) as ItemsPresenter, 62 | MultiSelectTreeView.RecursiveTreeViewItemEnumerable(treeView, false, false)); 63 | } 64 | 65 | public bool Select(MultiSelectTreeViewItem item) 66 | { 67 | if (IsControlKeyDown) 68 | { 69 | if (treeView.SelectedItems.Contains(item.DataContext)) 70 | { 71 | return Deselect(item, true); 72 | } 73 | else 74 | { 75 | var e = new PreviewSelectionChangedEventArgs(true, item.DataContext); 76 | OnPreviewSelectionChanged(e); 77 | if (e.CancelAny) 78 | { 79 | FocusHelper.Focus(item, true); 80 | return false; 81 | } 82 | 83 | return SelectCore(item); 84 | } 85 | } 86 | else 87 | { 88 | if (treeView.SelectedItems.Count == 1 && 89 | treeView.SelectedItems[0] == item.DataContext) 90 | { 91 | // Requested to select the single already-selected item. Don't change the selection. 92 | FocusHelper.Focus(item, true); 93 | lastShiftRoot = item.DataContext; 94 | return true; 95 | } 96 | else 97 | { 98 | return SelectCore(item); 99 | } 100 | } 101 | } 102 | 103 | internal bool SelectByRectangle(MultiSelectTreeViewItem item) 104 | { 105 | var e = new PreviewSelectionChangedEventArgs(true, item.DataContext); 106 | OnPreviewSelectionChanged(e); 107 | if (e.CancelAny) 108 | { 109 | lastShiftRoot = item.DataContext; 110 | return false; 111 | } 112 | 113 | if (!treeView.SelectedItems.Contains(item.DataContext)) 114 | { 115 | treeView.SelectedItems.Add(item.DataContext); 116 | } 117 | lastShiftRoot = item.DataContext; 118 | return true; 119 | } 120 | 121 | internal bool DeselectByRectangle(MultiSelectTreeViewItem item) 122 | { 123 | var e = new PreviewSelectionChangedEventArgs(false, item.DataContext); 124 | OnPreviewSelectionChanged(e); 125 | if (e.CancelAny) 126 | { 127 | lastShiftRoot = item.DataContext; 128 | return false; 129 | } 130 | 131 | treeView.SelectedItems.Remove(item.DataContext); 132 | if (item.DataContext == lastShiftRoot) 133 | { 134 | lastShiftRoot = null; 135 | } 136 | return true; 137 | } 138 | 139 | public bool SelectCore(MultiSelectTreeViewItem item) 140 | { 141 | if (IsControlKeyDown) 142 | { 143 | if (!treeView.SelectedItems.Contains(item.DataContext)) 144 | { 145 | treeView.SelectedItems.Add(item.DataContext); 146 | } 147 | lastShiftRoot = item.DataContext; 148 | } 149 | else if (IsShiftKeyDown && treeView.SelectedItems.Count > 0) 150 | { 151 | object firstSelectedItem = lastShiftRoot ?? treeView.SelectedItems.First(); 152 | MultiSelectTreeViewItem shiftRootItem = treeView.GetTreeViewItemsFor(new List { firstSelectedItem }).First(); 153 | 154 | var newSelection = treeView.GetNodesToSelectBetween(shiftRootItem, item).Select(n => n.DataContext).ToList(); 155 | // Make a copy of the list because we're modifying it while enumerating it 156 | var selectedItems = new object[treeView.SelectedItems.Count]; 157 | treeView.SelectedItems.CopyTo(selectedItems, 0); 158 | // Remove all items no longer selected 159 | foreach (var selItem in selectedItems.Where(i => !newSelection.Contains(i))) 160 | { 161 | var e = new PreviewSelectionChangedEventArgs(false, selItem); 162 | OnPreviewSelectionChanged(e); 163 | if (e.CancelAll) 164 | { 165 | FocusHelper.Focus(item); 166 | return false; 167 | } 168 | if (!e.CancelThis) 169 | { 170 | treeView.SelectedItems.Remove(selItem); 171 | } 172 | } 173 | // Add new selected items 174 | foreach (var newItem in newSelection.Where(i => !selectedItems.Contains(i))) 175 | { 176 | var e = new PreviewSelectionChangedEventArgs(true, newItem); 177 | OnPreviewSelectionChanged(e); 178 | if (e.CancelAll) 179 | { 180 | FocusHelper.Focus(item, true); 181 | return false; 182 | } 183 | if (!e.CancelThis) 184 | { 185 | treeView.SelectedItems.Add(newItem); 186 | } 187 | } 188 | } 189 | else 190 | { 191 | if (treeView.SelectedItems.Count > 0) 192 | { 193 | foreach (var selItem in new ArrayList(treeView.SelectedItems)) 194 | { 195 | var e2 = new PreviewSelectionChangedEventArgs(false, selItem); 196 | OnPreviewSelectionChanged(e2); 197 | if (e2.CancelAll) 198 | { 199 | FocusHelper.Focus(item); 200 | lastShiftRoot = item.DataContext; 201 | return false; 202 | } 203 | if (!e2.CancelThis) 204 | { 205 | treeView.SelectedItems.Remove(selItem); 206 | } 207 | } 208 | } 209 | 210 | var e = new PreviewSelectionChangedEventArgs(true, item.DataContext); 211 | OnPreviewSelectionChanged(e); 212 | if (e.CancelAny) 213 | { 214 | FocusHelper.Focus(item, true); 215 | lastShiftRoot = item.DataContext; 216 | return false; 217 | } 218 | 219 | treeView.SelectedItems.Add(item.DataContext); 220 | lastShiftRoot = item.DataContext; 221 | } 222 | 223 | FocusHelper.Focus(item, true); 224 | return true; 225 | } 226 | 227 | public bool SelectCurrentBySpace() 228 | { 229 | // Another item was focused by Ctrl+Arrow key 230 | var item = GetFocusedItem(); 231 | if (treeView.SelectedItems.Contains(item.DataContext)) 232 | { 233 | // With Ctrl key, toggle this item selection (deselect now). 234 | // Without Ctrl key, always select it (is already selected). 235 | if (IsControlKeyDown) 236 | { 237 | if (!Deselect(item, true)) return false; 238 | item.IsSelected = false; 239 | } 240 | } 241 | else 242 | { 243 | var e = new PreviewSelectionChangedEventArgs(true, item.DataContext); 244 | OnPreviewSelectionChanged(e); 245 | if (e.CancelAny) 246 | { 247 | FocusHelper.Focus(item, true); 248 | return false; 249 | } 250 | 251 | item.IsSelected = true; 252 | if (!treeView.SelectedItems.Contains(item.DataContext)) 253 | { 254 | treeView.SelectedItems.Add(item.DataContext); 255 | } 256 | } 257 | FocusHelper.Focus(item, true); 258 | return true; 259 | } 260 | 261 | private MultiSelectTreeViewItem GetFocusedItem() 262 | { 263 | foreach (var item in MultiSelectTreeView.RecursiveTreeViewItemEnumerable(treeView, false, false)) 264 | { 265 | if (item.IsFocused) return item; 266 | } 267 | return null; 268 | } 269 | 270 | private bool SelectFromKey(MultiSelectTreeViewItem item) 271 | { 272 | if (item == null) 273 | { 274 | return false; 275 | } 276 | 277 | // If Ctrl is pressed just focus it, so it can be selected by Space. Otherwise select it. 278 | if (IsControlKeyDown) 279 | { 280 | FocusHelper.Focus(item, true); 281 | return true; 282 | } 283 | else 284 | { 285 | return SelectCore(item); 286 | } 287 | } 288 | 289 | public bool SelectNextFromKey() 290 | { 291 | List items = MultiSelectTreeView.RecursiveTreeViewItemEnumerable(treeView, false, false).ToList(); 292 | MultiSelectTreeViewItem item = treeView.GetNextItem(GetFocusedItem(), items); 293 | return SelectFromKey(item); 294 | } 295 | 296 | public bool SelectPreviousFromKey() 297 | { 298 | List items = MultiSelectTreeView.RecursiveTreeViewItemEnumerable(treeView, false, false).ToList(); 299 | MultiSelectTreeViewItem item = treeView.GetPreviousItem(GetFocusedItem(), items); 300 | return SelectFromKey(item); 301 | } 302 | 303 | public bool SelectFirstFromKey() 304 | { 305 | List items = MultiSelectTreeView.RecursiveTreeViewItemEnumerable(treeView, false, false).ToList(); 306 | MultiSelectTreeViewItem item = treeView.GetFirstItem(items); 307 | return SelectFromKey(item); 308 | } 309 | 310 | public bool SelectLastFromKey() 311 | { 312 | List items = MultiSelectTreeView.RecursiveTreeViewItemEnumerable(treeView, false, false).ToList(); 313 | MultiSelectTreeViewItem item = treeView.GetLastItem(items); 314 | return SelectFromKey(item); 315 | } 316 | 317 | private bool SelectPageUpDown(bool down) 318 | { 319 | List items = MultiSelectTreeView.RecursiveTreeViewItemEnumerable(treeView, false, false).ToList(); 320 | MultiSelectTreeViewItem item = GetFocusedItem(); 321 | if (item == null) 322 | { 323 | return down ? SelectLastFromKey() : SelectFirstFromKey(); 324 | } 325 | 326 | double targetY = item.TransformToAncestor(treeView).Transform(new Point()).Y; 327 | FrameworkElement itemContent = (FrameworkElement) item.Template.FindName("headerBorder", item); 328 | double offset = treeView.ActualHeight - 2 * itemContent.ActualHeight; 329 | if (!down) offset = -offset; 330 | targetY += offset; 331 | while (true) 332 | { 333 | var newItem = down ? treeView.GetNextItem(item, items) : treeView.GetPreviousItem(item, items); 334 | if (newItem == null) break; 335 | item = newItem; 336 | double itemY = item.TransformToAncestor(treeView).Transform(new Point()).Y; 337 | if (down && itemY > targetY || 338 | !down && itemY < targetY) 339 | { 340 | break; 341 | } 342 | } 343 | return SelectFromKey(item); 344 | } 345 | 346 | public bool SelectPageUpFromKey() 347 | { 348 | return SelectPageUpDown(false); 349 | } 350 | 351 | public bool SelectPageDownFromKey() 352 | { 353 | return SelectPageUpDown(true); 354 | } 355 | 356 | public bool SelectAllFromKey() 357 | { 358 | var items = MultiSelectTreeView.RecursiveTreeViewItemEnumerable(treeView, false, false).ToList(); 359 | // Add new selected items 360 | foreach (var item in items.Where(i => !treeView.SelectedItems.Contains(i.DataContext))) 361 | { 362 | var e = new PreviewSelectionChangedEventArgs(true, item.DataContext); 363 | OnPreviewSelectionChanged(e); 364 | if (e.CancelAll) 365 | { 366 | return false; 367 | } 368 | if (!e.CancelThis) 369 | { 370 | treeView.SelectedItems.Add(item.DataContext); 371 | } 372 | } 373 | return true; 374 | } 375 | 376 | public bool SelectParentFromKey() 377 | { 378 | DependencyObject parent = GetFocusedItem(); 379 | while (parent != null) 380 | { 381 | parent = VisualTreeHelper.GetParent(parent); 382 | if (parent is MultiSelectTreeViewItem) break; 383 | } 384 | return SelectFromKey(parent as MultiSelectTreeViewItem); 385 | } 386 | 387 | public bool Deselect(MultiSelectTreeViewItem item, bool bringIntoView = false) 388 | { 389 | var e = new PreviewSelectionChangedEventArgs(false, item.DataContext); 390 | OnPreviewSelectionChanged(e); 391 | if (e.CancelAny) return false; 392 | 393 | treeView.SelectedItems.Remove(item.DataContext); 394 | if (item.DataContext == lastShiftRoot) 395 | { 396 | lastShiftRoot = null; 397 | } 398 | FocusHelper.Focus(item, bringIntoView); 399 | return true; 400 | } 401 | 402 | public void Dispose() 403 | { 404 | if (borderSelectionLogic != null) 405 | { 406 | borderSelectionLogic.Dispose(); 407 | borderSelectionLogic = null; 408 | } 409 | 410 | GC.SuppressFinalize(this); 411 | } 412 | 413 | protected void OnPreviewSelectionChanged(PreviewSelectionChangedEventArgs e) 414 | { 415 | var handler = PreviewSelectionChanged; 416 | if (handler != null) 417 | { 418 | handler(this, e); 419 | LastCancelAll = e.CancelAll; 420 | } 421 | } 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Controls/SelectionSingle.cs: -------------------------------------------------------------------------------- 1 | namespace System.Windows.Controls 2 | { 3 | public class SelectionSingle 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Controls/ThicknessLeftConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Data; 2 | 3 | namespace System.Windows.Controls 4 | { 5 | class ThicknessLeftConverter : IValueConverter 6 | { 7 | public object Convert(object value, Type targetType, object parameter, Globalization.CultureInfo culture) 8 | { 9 | if (value is int) 10 | return new Thickness { Left = (int) value }; 11 | if (value is double) 12 | return new Thickness { Left = (double) value }; 13 | return new Thickness(); 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, Globalization.CultureInfo culture) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /MultiSelectTreeView/MultiSelectTreeView.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {2854814F-EA3C-41D4-AA94-460C4694F430} 9 | Library 10 | Properties 11 | System.Windows 12 | MultiSelectTreeView 13 | v4.0 14 | Client 15 | 512 16 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 4 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | true 32 | bin\Debug\ 33 | DEBUG;TRACE 34 | full 35 | AnyCPU 36 | prompt 37 | true 38 | true 39 | 40 | 41 | bin\Release\ 42 | TRACE 43 | true 44 | pdbonly 45 | AnyCPU 46 | prompt 47 | false 48 | false 49 | 50 | 51 | MultiSelectTreeView.ico 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 | Designer 78 | MSBuild:Compile 79 | 80 | 81 | Designer 82 | MSBuild:Compile 83 | 84 | 85 | Designer 86 | MSBuild:Compile 87 | 88 | 89 | Designer 90 | MSBuild:Compile 91 | 92 | 93 | MSBuild:Compile 94 | Designer 95 | 96 | 97 | MSBuild:Compile 98 | Designer 99 | 100 | 101 | MSBuild:Compile 102 | Designer 103 | 104 | 105 | MSBuild:Compile 106 | Designer 107 | 108 | 109 | MSBuild:Compile 110 | Designer 111 | 112 | 113 | MSBuild:Compile 114 | Designer 115 | 116 | 117 | MSBuild:Compile 118 | Designer 119 | 120 | 121 | MSBuild:Compile 122 | Designer 123 | 124 | 125 | MSBuild:Compile 126 | Designer 127 | 128 | 129 | Designer 130 | MSBuild:Compile 131 | 132 | 133 | Designer 134 | MSBuild:Compile 135 | 136 | 137 | Designer 138 | MSBuild:Compile 139 | 140 | 141 | 142 | 143 | 144 | Code 145 | 146 | 147 | True 148 | True 149 | Resources.resx 150 | 151 | 152 | ResXFileCodeGenerator 153 | Resources.Designer.cs 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | del /s /f /q $(ProjectDir)\NuGet\lib 165 | xcopy /y $(TargetPath) $(ProjectDir)NuGet\lib\net40\ 166 | $(ProjectDir)tools\NuGet.exe pack $(ProjectDir)NuGet\MultiSelectTreeView.nuspec -basepath $(ProjectDir)NuGet -outputdir $(TargetDir) 167 | 168 | 175 | -------------------------------------------------------------------------------- /MultiSelectTreeView/MultiSelectTreeView.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygoe/MultiSelectTreeView/8dadce5dfa1fce0216c906921f0edf9551dbc457/MultiSelectTreeView/MultiSelectTreeView.ico -------------------------------------------------------------------------------- /MultiSelectTreeView/Nuget/MultiSelectTreeView.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MultiSelectTreeView 5 | MultiSelectTreeView 6 | 1.0.9 7 | Yves Goergen, Goroll 8 | Yves Goergen 9 | http://unclassified.software/source/multiselecttreeview 10 | https://github.com/ygoe/MultiSelectTreeView/blob/master/LICENSE 11 | false 12 | A WPF TreeView control with support for multiple selection. 13 | MultiSelectTreeView is a .NET/WPF control that displays a hierarchical tree of items that can be browsed, selected, collapsed and edited like in a normal tree control. This control overcomes some of the limitations that the TreeView control included in WPF has. Most prominently, it adds multiple selection and is easier to style in several aspects. It features the Windows 7 Aero theme very closely and adapts to other Windows themes (Luna, Royale, Classic, Aero2 for Windows 10). Mouse and keyboard control is largely the same as in Windows Explorer. 14 | Yves Goergen, Goroll 15 | multiselect treeview wpf tree control themed 16 | 17 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | [assembly: AssemblyProduct("MultiSelectTreeView WPF control")] 8 | [assembly: AssemblyTitle("WPF control library with a TreeView supporting multiple selection.")] 9 | 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyCopyright("© 2012–2017 Yves Goergen, Goroll")] 12 | 13 | // IMPORTANT: When changing the version number, also update the NuGet package and the version history below. 14 | [assembly: AssemblyVersion("1.0.9.0")] 15 | 16 | [assembly: ComVisible(false)] 17 | [assembly: ThemeInfo( 18 | // Where theme specific resource dictionaries are located 19 | // (used if a resource is not found in the page, or application resource dictionaries) 20 | ResourceDictionaryLocation.SourceAssembly, 21 | // Where the generic resource dictionary is located 22 | // (used if a resource is not found in the page, app, or any theme specific resource dictionaries) 23 | ResourceDictionaryLocation.SourceAssembly 24 | )] 25 | 26 | // Change history: 27 | // 28 | // 1.0.9 - 2017-03-20 29 | // * Improved contrast of inactive selection on Windows 7 and Windows 10 30 | // 31 | // 1.0.8 - 2016-12-27 32 | // * Fix: #35 (Selecting items without a mouse click does not update the lastShiftRoot…) 33 | // 34 | // 1.0.7 - 2015-06-09 35 | // * Fix: ClearSelection() throws exception if a PreviewSelectionChanged handler changes the selection 36 | // 37 | // 1.0.6 - 2015-04-09 38 | // * Fix: Treeview disposed on tabhide 39 | // * Non-Ctrl click in the background clears selection 40 | // 41 | // 1.0.5 - 2015-03-02 42 | // * Fix: MultiSelectTreeView does not use template from ItemTemplateSelector 43 | // 44 | // 1.0.4 - 2014-11-17 45 | // * Changed Windows 8 Aero2 theme to match native style more closely (hover colours different from selected) 46 | // 47 | // 1.0.3 - 2014-07-28 48 | // * Added FallbackValues to several XAML bindings to avoid "Cannot find source for binding" errors and warnings 49 | // 50 | // 1.0.2 - 2014-06-26 51 | // * Only expand/collapse item on double-click with the left mouse button, not others 52 | // 53 | // 1.0.1 - 2014-06-07 54 | // * Deselect hidden items and all children of hidden and collapsed items 55 | // 56 | // 1.0 - past (see commit history) 57 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.239 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace System.Windows.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("System.Windows.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/Aero.NormalColor.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/Aero2.NormalColor.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/Classic.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/EditTextBox.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 39 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/Luna.Homestead.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/Luna.Metallic.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/Luna.NormalColor.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/MultiSelectTreeView.Aero.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/MultiSelectTreeView.Aero2.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/MultiSelectTreeView.Classic.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 51 | 52 | 53 | 54 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/MultiSelectTreeViewItem.Luna.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 127 | 128 | 129 | 130 | 131 | 139 | 149 | 150 | 151 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 179 | 181 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 200 | 203 | 204 | 206 | 208 | 210 | 211 | 212 | 213 | 214 | 215 | 218 | 221 | 224 | 225 | 227 | 229 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 252 | 253 | 254 | 255 | 256 | 257 | 259 | 261 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | -------------------------------------------------------------------------------- /MultiSelectTreeView/Themes/Royale.NormalColor.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MultiSelectTreeView/tools/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygoe/MultiSelectTreeView/8dadce5dfa1fce0216c906921f0edf9551dbc457/MultiSelectTreeView/tools/NuGet.exe -------------------------------------------------------------------------------- /Pictures/W7StyleSample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ygoe/MultiSelectTreeView/8dadce5dfa1fce0216c906921f0edf9551dbc457/Pictures/W7StyleSample.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiSelectTreeView 2 | 3 | A WPF TreeView control with support for multiple selection. 4 | 5 | See http://unclassified.software/source/multiselecttreeview for further information. 6 | 7 | MultiSelectTreeView is a .NET/WPF control that displays a hierarchical tree of items that can be browsed, selected, collapsed and edited like in a normal tree control. This control overcomes some of the limitations that the TreeView control included in WPF has. Most prominently, it adds multiple selection and is easier to style in several aspects. It features the Windows 7 Aero theme very closely and adapts to other Windows themes (Luna, Royale, Classic, Aero2). 8 | 9 | The code is based on the TreeViewEx project on CodePlex (http://treeviewex.codeplex.com/) and released under the MIT licence. I picked up the code and made several enhancements and bug fixes to make it a more robust and complete solution. 10 | 11 | The current development status is considered usable, there are no known major bugs or missing parts that need to be fixed. The included test cases are outdated and likely won't work anymore. To play with the control and its features, you can run the Demo application from the solution. 12 | 13 | You can also install this package from NuGet: https://www.nuget.org/packages/MultiSelectTreeView 14 | -------------------------------------------------------------------------------- /TraceAndTestImpact.testsettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | These are test settings for Trace and Test Impact. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | --------------------------------------------------------------------------------