├── .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