1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.View
7 using System.Collections.ObjectModel;
8 using System.ComponentModel;
11 using System.Reflection;
13 using System.Windows.Automation.Peers;
14 using System.Windows.Controls;
15 using System.Windows.Input;
16 using System.Collections.Specialized;
17 using System.Collections.Generic;
18 using System.Diagnostics.CodeAnalysis;
20 using System.ComponentModel.Design;
21 using System.Activities.Presentation.Hosting;
22 using System.Windows.Threading;
23 using Microsoft.Activities.Presentation;
25 internal sealed partial class TypeBrowser : DialogWindow
27 static readonly DependencyProperty SelectedTypeProperty =
28 DependencyProperty.Register("SelectedType",
31 new UIPropertyMetadata());
33 static readonly DependencyProperty HasGenericTypesProperty =
34 DependencyProperty.Register("HasGenericTypes",
38 static readonly DependencyProperty GenericTypeNameProperty =
39 DependencyProperty.Register("GenericTypeName",
43 static readonly DependencyProperty GenericTypeMappingProperty =
44 DependencyProperty.Register("GenericTypeMapping",
45 typeof(ObservableCollection<TypeKeyValue>),
48 static readonly DependencyProperty ConcreteTypeProperty =
49 DependencyProperty.Register("ConcreteType",
53 static Size size = Size.Empty;
55 SearchAction currentSearch = null;
57 ObservableCollection<AssemblyNode> localAssemblies;
58 ObservableCollection<AssemblyNode> referenceAssemblies;
59 AssemblyContextControlItem assemblyContext;
60 Func<Type, bool> filter;
61 DesignerPerfEventProvider perfEventProvider;
63 public TypeBrowser(AssemblyContextControlItem assemblyContext, EditingContext context, Func<Type, bool> filter)
65 this.assemblyContext = assemblyContext;
66 this.Context = context;
68 SetValue(GenericTypeMappingProperty, new ObservableCollection<TypeKeyValue>());
69 GenericTypeMapping.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(GenericTypeMappingCollectionChanged);
70 InitializeComponent();
71 this.typeEntryTextBox.Focus();
75 this.Height = size.Height;
76 this.Width = size.Width;
79 this.SizeChanged += new SizeChangedEventHandler(TypeBrowser_SizeChanged);
81 this.HelpKeyword = HelpKeywords.TypeBrowser;
82 this.perfEventProvider = new DesignerPerfEventProvider();
85 static void TypeBrowser_SizeChanged(object sender, SizeChangedEventArgs e)
87 TypeBrowser.size = e.NewSize;
92 get { return (Type)GetValue(SelectedTypeProperty); }
93 set { SetValue(SelectedTypeProperty, value); }
98 get { return (bool)GetValue(HasGenericTypesProperty); }
99 set { SetValue(HasGenericTypesProperty, value); }
102 string GenericTypeName
104 get { return (string)GetValue(GenericTypeNameProperty); }
105 set { SetValue(GenericTypeNameProperty, value); }
108 ObservableCollection<TypeKeyValue> GenericTypeMapping
110 get { return (ObservableCollection<TypeKeyValue>)GetValue(GenericTypeMappingProperty); }
111 set { SetValue(GenericTypeMappingProperty, value); }
114 public Type ConcreteType
116 get { return (Type)GetValue(ConcreteTypeProperty); }
117 private set { SetValue(ConcreteTypeProperty, value); }
120 public ObservableCollection<AssemblyNode> LocalAssemblies
124 if (null == this.localAssemblies)
126 this.localAssemblies = new ObservableCollection<AssemblyNode>();
127 if (null != this.assemblyContext)
129 if (null != this.assemblyContext.LocalAssemblyName)
131 IMultiTargetingSupportService multiTargetingSupportService = this.Context.Services.GetService<IMultiTargetingSupportService>();
132 Assembly local = AssemblyContextControlItem.GetAssembly(this.assemblyContext.LocalAssemblyName, multiTargetingSupportService);
135 this.localAssemblies.Add(new AssemblyNode(local, true, this.filter, this.Context));
140 if (this.localAssemblies.Count == 0)
142 this.LocalAssembly.Visibility = Visibility.Collapsed;
146 this.LocalAssembly.Visibility = Visibility.Visible;
149 return this.localAssemblies;
153 public ObservableCollection<AssemblyNode> ReferenceAssemblies
157 if (null == this.referenceAssemblies)
159 this.referenceAssemblies = new ObservableCollection<AssemblyNode>();
160 if (null != this.assemblyContext)
162 IMultiTargetingSupportService multiTargetingSupportService = this.Context.Services.GetService<IMultiTargetingSupportService>();
163 IEnumerable<Assembly> assemblies = this.assemblyContext.GetEnvironmentAssemblies(multiTargetingSupportService);
164 foreach (Assembly assembly in assemblies.OrderBy<Assembly, string>(p => p.FullName))
166 this.referenceAssemblies.Add(new AssemblyNode(assembly, false, this.filter, this.Context));
170 return this.referenceAssemblies;
174 public bool? ShowDialog(DependencyObject owner)
176 WindowHelperService.TrySetWindowOwner(owner, this.Context, this);
177 this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { this.perfEventProvider.TypeBrowserApplicationIdleAfterShowDialog(); }));
178 return base.ShowDialog();
181 protected override void OnKeyDown(KeyEventArgs e)
183 if (e.Key == Key.Escape)
185 this.DialogResult = false;
188 else if (e.Key == Key.Enter && null != SelectedType)
199 protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
201 return new UIElementAutomationPeer(this);
204 private void GenericTypeMappingCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
206 this.HasGenericTypes = GenericTypeMapping.Count > 0 ? true : false;
207 if (this.HasGenericTypes)
209 string strName = this.SelectedType.FullName;
210 this.GenericTypeName = strName.Substring(0, strName.Length - 2) + " <";
214 this.GenericTypeName = null;
218 private Type ResolveType(out string errorTitle, out string errorMessage)
222 Type result = this.SelectedType;
226 IMultiTargetingSupportService multiTargetingSupport = this.Context.Services.GetService<IMultiTargetingSupportService>();
227 if (multiTargetingSupport != null)
229 result = multiTargetingSupport.GetRuntimeType(result);
234 errorTitle = SR.TypeBrowserErrorMessageTitle;
235 errorMessage = SR.TypeBrowserError;
239 if (result.IsGenericTypeDefinition)
242 //get number of generic parameters in edited type
243 Type[] arguments = new Type[this.GenericTypeMapping.Count];
245 //for each argument, get resolved type
246 for (int i = 0; i < this.GenericTypeMapping.Count && isValid; ++i)
248 arguments[i] = this.GenericTypeMapping[i].GetConcreteType();
249 if (multiTargetingSupport != null && arguments[i] != null)
251 arguments[i] = multiTargetingSupport.GetRuntimeType(arguments[i]);
253 isValid = isValid && (null != arguments[i]);
256 //if all parameters are resolved, create concrete type
259 result = result.MakeGenericType(arguments);
263 errorTitle = SR.TypeBrowserErrorMessageTitle;
264 errorMessage = SR.TypeResolverError;
269 catch (ArgumentException err)
271 errorTitle = err.GetType().Name;
272 errorMessage = err.Message;
279 private void OnOkClick(object sender, RoutedEventArgs args)
284 private void OnCancelClick(object sender, RoutedEventArgs args)
286 this.DialogResult = false;
289 private void OnTypeDoubleClick(object sender, RoutedEventArgs args)
291 if (((System.Windows.Input.MouseButtonEventArgs)(args)).ChangedButton == MouseButton.Left)
293 TypeNode entry = ((TreeViewItem)sender).Header as TypeNode;
294 if (null != entry && entry.Data is Type)
302 private void OnDialogClose()
304 string errorTitle = null;
305 string errorMessage = null;
307 Type type = ResolveType(out errorTitle, out errorMessage);
310 this.ConcreteType = type;
311 this.DialogResult = true;
312 this.perfEventProvider.TypeBrowserOkPressed();
316 MessageBox.Show(errorMessage, errorTitle, MessageBoxButton.OK, MessageBoxImage.Error);
320 private void OnTypeBrowserClickStart(object sender, RoutedEventArgs args)
322 TreeViewItem item = sender as TreeViewItem;
323 NamespaceNode ns = null;
326 ns = item.Header as NamespaceNode;
329 if (null != ns && null == ns.Tag)
331 ns.Tag = string.Empty;
332 Mouse.OverrideCursor = Cursors.Wait;
336 private void OnTypeBrowserClickEnd(object sender, RoutedEventArgs args)
338 TreeViewItem item = sender as TreeViewItem;
339 if (null != item && item.Header is AssemblyNode && null != Mouse.OverrideCursor)
341 Mouse.OverrideCursor = null;
345 private void OnTypeSearchTextChanged(object sender, TextChangedEventArgs e)
347 string searchText = ((TextBox)sender).Text;
349 SearchAction newSearch = new SearchAction(searchText, this.localAssemblies, this.referenceAssemblies);
350 newSearch.Completed += delegate(object s, EventArgs args)
352 SearchAction senderAction = s as SearchAction;
353 SearchAction currentAction = this.currentSearch;
354 this.currentSearch = null;
356 if (senderAction == currentAction)
358 TypeNode match = ((SearchActionEventArgs)args).Result as TypeNode;
360 UpdateSelectedItem(match);
363 match.IsSelected = true;
368 if (this.currentSearch != null)
370 this.currentSearch.Abort();
375 this.currentSearch = newSearch;
376 this.currentSearch.Run();
379 private void ClearSelection()
381 TypeNode currentSelection = this.typesTreeView.SelectedItem as TypeNode;
382 if (currentSelection != null)
384 currentSelection.IsSelected = false;
388 private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
390 TypeNode entry = ((System.Windows.Controls.TreeView)sender).SelectedItem as TypeNode;
391 if (entry != null && this.SelectedType != entry.Data as Type)
393 UpdateSelectedItem(entry);
394 if (null != this.SelectedType)
396 typeEntryTextBox.TextChanged -= new TextChangedEventHandler(OnTypeSearchTextChanged);
397 typeEntryTextBox.Text = TypeNameHelper.GetDisplayName(this.SelectedType, true);
398 typeEntryTextBox.TextChanged += new TextChangedEventHandler(OnTypeSearchTextChanged);
403 private void UpdateSelectedItem(TypeNode entry)
405 GenericTypeMapping.Clear();
407 SelectedType = (null != entry ? entry.Data as Type : null);
409 if (null != this.SelectedType)
411 if (this.SelectedType.IsGenericTypeDefinition)
413 this.ConcreteType = null;
414 Type[] generics = this.SelectedType.GetGenericArguments();
415 foreach (Type t in generics)
417 this.GenericTypeMapping.Add(new TypeKeyValue(t, null));
422 this.ConcreteType = this.SelectedType;
427 this.ConcreteType = null;
431 internal class Node : INotifyPropertyChanged
433 public event PropertyChangedEventHandler PropertyChanged;
435 private bool isExpanded;
436 private bool isSelected;
437 private Visibility visibility;
441 this.isExpanded = false;
442 this.isSelected = false;
443 this.visibility = Visibility.Visible;
446 protected void Notify(string property)
448 if (PropertyChanged != null)
450 PropertyChanged(this, new PropertyChangedEventArgs(property));
454 public bool IsExpanded
458 return this.isExpanded;
462 if (this.isExpanded != value)
464 this.isExpanded = value;
465 Notify("IsExpanded");
470 public bool IsSelected
474 return this.isSelected;
478 if (this.isSelected != value)
480 this.isSelected = value;
481 Notify("IsSelected");
486 public Visibility Visibility
490 return this.visibility;
494 if (this.visibility != value)
496 this.visibility = value;
497 Notify("Visibility");
509 internal class AssemblyNode : Node
514 Func<Type, bool> filter;
515 EditingContext context;
517 public AssemblyNode(Assembly assembly, bool isLocal, Func<Type, bool> filter, EditingContext context)
519 if (null == assembly)
521 throw FxTrace.Exception.AsError(new ArgumentNullException("assembly"));
523 this.assembly = assembly;
524 this.isLocal = isLocal;
525 this.displayName = GetDisplayName(this.assembly.GetName());
526 this.filter = filter;
527 this.context = context;
530 private static string GetDisplayName(AssemblyName name)
532 StringBuilder sb = new StringBuilder();
533 if (name != null && name.Name != null)
535 sb.Append(name.Name);
536 if (name.Version != null)
539 sb.Append(name.Version.Major);
541 sb.Append(name.Version.Minor);
543 sb.Append(name.Version.Build);
545 sb.Append(name.Version.Revision);
549 return sb.ToString();
552 public string DisplayName
554 get { return this.displayName; }
557 private ObservableCollection<NamespaceNode> namespaces;
558 public ObservableCollection<NamespaceNode> Namespaces
562 if (namespaces == null)
564 namespaces = new ObservableCollection<NamespaceNode>();
568 Func<Type, bool> typeFilter = this.filter;
569 IMultiTargetingSupportService multiTargetingSupport = this.context.Services.GetService<IMultiTargetingSupportService>();
570 if (multiTargetingSupport != null && typeFilter != null)
572 typeFilter = (type) => this.filter(multiTargetingSupport.GetRuntimeType(type));
576 from type in (this.isLocal ? this.assembly.GetTypes() : this.assembly.GetExportedTypes())
577 where (type.IsPublic && type.IsVisible && (typeFilter == null || typeFilter(type)))
578 orderby type.Namespace, type.Name
581 NamespaceNode lastNamespace = null;
582 foreach (Type type in exportedTypes)
584 if (lastNamespace == null || !StringComparer.OrdinalIgnoreCase.Equals(lastNamespace.DisplayName, type.Namespace))
586 lastNamespace = new NamespaceNode(type.Namespace);
587 namespaces.Add(lastNamespace);
590 lastNamespace.Types.Add(new TypeNode(type));
593 catch (NotSupportedException)
595 //Dynamic (in memory) assemblies will throw exception when this method is called
596 //that's the reason i'm swollowing that exception
606 get { return this.displayName; }
609 public override string ToString()
611 return this.displayName;
615 internal class NamespaceNode : Node
618 private string displayName;
619 public string DisplayName
623 return this.displayName;
627 private ObservableCollection<TypeNode> types;
628 public ObservableCollection<TypeNode> Types
638 get { return this.displayName; }
641 public NamespaceNode(string name)
643 this.displayName = name;
644 this.types = new ObservableCollection<TypeNode>();
648 internal class TypeNode : Node
652 private string displayName;
653 public string DisplayName
657 return this.displayName;
661 private string fullName;
662 public string FullName
666 return this.fullName;
672 get { return this.type; }
675 public TypeNode(Type type)
678 this.displayName = TypeNameHelper.GetDisplayName(type, false);
679 this.fullName = TypeNameHelper.GetDisplayName(type, true);
683 private class SearchActionEventArgs : EventArgs
692 private class SearchAction
694 private string searchText;
695 Collection<AssemblyNode>[] range;
696 DispatcherOperation dispatcherOperation;
698 public event EventHandler Completed;
700 public SearchAction(string searchText, Collection<AssemblyNode> localAssemblies, Collection<AssemblyNode> referenceAssemblies)
702 this.searchText = searchText;
703 this.range = new Collection<AssemblyNode>[] { localAssemblies, referenceAssemblies };
709 this.dispatcherOperation = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Input, new Func<TypeNode>(this.OnRun));
710 this.dispatcherOperation.Completed += this.OnCompleted;
715 if (this.dispatcherOperation != null)
717 return this.dispatcherOperation.Abort();
722 private TypeNode OnRun()
724 bool noSearch = string.IsNullOrEmpty(searchText);
725 Func<TypeNode, string, bool> matchAlgorithm = SearchAction.MatchShortName;
727 TypeNode match = null;
728 TypeNode firstCandidate = null;
729 bool tooManyCandiates = false;
731 if (!noSearch && searchText.Contains('.'))
733 matchAlgorithm = SearchAction.MatchFullName;
736 foreach (Collection<AssemblyNode> assemblies in this.range)
738 foreach (AssemblyNode assembly in assemblies)
740 Visibility assemblyVisibility = Visibility.Collapsed;
741 bool assemblyIsExpanded = false;
745 assemblyVisibility = Visibility.Visible;
748 foreach (NamespaceNode ns in assembly.Namespaces)
750 Visibility namespaceVisibility = Visibility.Collapsed;
751 bool namespaceIsExpanded = false;
755 namespaceVisibility = Visibility.Visible;
758 foreach (TypeNode entry in ns.Types)
762 entry.Visibility = Visibility.Visible;
764 else if (matchAlgorithm(entry, searchText))
766 entry.Visibility = Visibility.Visible;
767 assemblyVisibility = Visibility.Visible;
768 assemblyIsExpanded = true;
769 namespaceVisibility = Visibility.Visible;
770 namespaceIsExpanded = true;
772 if (string.Equals(searchText, entry.FullName, StringComparison.OrdinalIgnoreCase))
777 if (firstCandidate == null)
779 firstCandidate = entry;
781 else if (!tooManyCandiates)
783 tooManyCandiates = true;
788 entry.Visibility = Visibility.Collapsed;
792 if (searchText.Contains('.') && ns.DisplayName != null && ns.DisplayName.StartsWith(searchText, StringComparison.OrdinalIgnoreCase))
794 namespaceIsExpanded = false;
797 if (namespaceIsExpanded)
799 ns.Tag = string.Empty;
802 ns.Visibility = namespaceVisibility;
803 ns.IsExpanded = namespaceIsExpanded;
806 assembly.Visibility = assemblyVisibility;
807 assembly.IsExpanded = assemblyIsExpanded;
811 if (match == null && !tooManyCandiates)
813 match = firstCandidate;
819 private void OnCompleted(object sender, EventArgs args)
821 this.dispatcherOperation.Completed -= this.OnCompleted;
822 if (this.Completed != null)
824 SearchActionEventArgs arg = new SearchActionEventArgs();
825 arg.Result = this.dispatcherOperation.Result;
826 this.Completed(this, arg);
830 // "abc.def" matches regex ^.*\.abc\.def.* or ^abc\.def.*, but does not match ^.*[^.]abc\.def.*
831 private static bool MatchFullName(TypeNode type, string searchText)
833 if (searchText.StartsWith(".", StringComparison.OrdinalIgnoreCase))
835 return -1 != type.FullName.IndexOf(searchText, StringComparison.OrdinalIgnoreCase);
838 if (type.FullName.StartsWith(searchText, StringComparison.OrdinalIgnoreCase))
843 if (-1 != type.FullName.IndexOf("." + searchText, StringComparison.OrdinalIgnoreCase))
851 private static bool MatchShortName(TypeNode type, string searchText)
853 return type.DisplayName.StartsWith(searchText, StringComparison.OrdinalIgnoreCase);