1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
5 namespace System.Activities.Presentation.View
8 using System.Collections.Generic;
9 using System.Collections.ObjectModel;
10 using System.Collections.Specialized;
11 using System.ComponentModel;
12 using System.Globalization;
15 using System.Windows.Automation.Peers;
16 using System.Windows.Controls;
17 using System.Windows.Data;
18 using System.Windows.Input;
20 using System.Windows.Automation;
21 using System.Diagnostics.CodeAnalysis;
22 using System.Windows.Threading;
23 using System.Activities.Presentation.Hosting;
24 using Microsoft.Activities.Presentation;
26 // This control presents a System.Type as textblock, which is editable on click, or F2.
27 public sealed partial class TypePresenter : ContentControl, INotifyPropertyChanged
29 public static readonly DependencyProperty ContextProperty =
30 DependencyProperty.Register("Context",
31 typeof(EditingContext),
32 typeof(TypePresenter),
33 new PropertyMetadata(new PropertyChangedCallback(OnContextChanged)));
35 public static readonly DependencyProperty AllowNullProperty =
36 DependencyProperty.Register("AllowNull",
38 typeof(TypePresenter),
39 new PropertyMetadata(false, OnAllowNullChanged));
41 public static readonly DependencyProperty BrowseTypeDirectlyProperty =
42 DependencyProperty.Register("BrowseTypeDirectly",
44 typeof(TypePresenter),
45 new PropertyMetadata(false, OnBrowseTypeDirectlyChanged));
47 public static readonly DependencyProperty TypeProperty =
48 DependencyProperty.Register("Type",
50 typeof(TypePresenter),
51 new PropertyMetadata(null, new PropertyChangedCallback(OnTypeChanged)));
53 public static readonly DependencyProperty LabelProperty =
54 DependencyProperty.Register("Label",
56 typeof(TypePresenter),
57 new PropertyMetadata(string.Empty));
59 public static readonly DependencyProperty FilterProperty =
60 DependencyProperty.Register("Filter",
61 typeof(Func<Type, bool>),
62 typeof(TypePresenter),
63 new PropertyMetadata(new PropertyChangedCallback(OnFilterChanged)));
65 static readonly DependencyPropertyKey TextPropertyKey = DependencyProperty.RegisterReadOnly(
68 typeof(TypePresenter),
69 new UIPropertyMetadata(null));
71 public static readonly DependencyProperty TextProperty = TextPropertyKey.DependencyProperty;
73 public static readonly DependencyProperty MostRecentlyUsedTypesProperty =
74 DependencyProperty.Register("MostRecentlyUsedTypes",
75 typeof(ObservableCollection<Type>),
76 typeof(TypePresenter),
77 new PropertyMetadata(TypePresenter.DefaultMostRecentlyUsedTypes, new PropertyChangedCallback(OnMostRecentlyUsedTypesPropertyChanged), new CoerceValueCallback(OnCoerceMostRecentlyUsedTypes)));
79 public static readonly DependencyProperty CenterActivityTypeResolverDialogProperty =
80 DependencyProperty.Register("CenterActivityTypeResolverDialog",
82 typeof(TypePresenter),
83 new PropertyMetadata(true));
85 public static readonly DependencyProperty CenterTypeBrowserDialogProperty =
86 DependencyProperty.Register("CenterTypeBrowserDialog",
88 typeof(TypePresenter),
89 new PropertyMetadata(true));
91 public static readonly RoutedEvent TypeBrowserOpenedEvent = EventManager.RegisterRoutedEvent(
93 RoutingStrategy.Bubble,
94 typeof(RoutedEventHandler),
95 typeof(TypePresenter));
97 public static readonly RoutedEvent TypeBrowserClosedEvent = EventManager.RegisterRoutedEvent(
99 RoutingStrategy.Bubble,
100 typeof(RoutedEventHandler),
101 typeof(TypePresenter));
103 public static readonly RoutedEvent TypeChangedEvent = EventManager.RegisterRoutedEvent(
105 RoutingStrategy.Bubble,
106 typeof(RoutedEventHandler),
107 typeof(TypePresenter));
109 static List<Type> defaultTypes = null;
110 static ObservableCollection<Type> defaultMostRecentlyUsedTypes;
112 internal static List<Type> DefaultTypes
116 if (defaultTypes == null)
118 defaultTypes = new List<Type>
130 public static ObservableCollection<Type> DefaultMostRecentlyUsedTypes
134 if (defaultMostRecentlyUsedTypes == null)
136 defaultMostRecentlyUsedTypes = new ObservableCollection<Type>(DefaultTypes);
138 return defaultMostRecentlyUsedTypes;
142 [SuppressMessage(FxCop.Category.Usage, FxCop.Rule.CollectionPropertiesShouldBeReadOnly,
143 Justification = "Setter is provided to bind data on this property.")]
144 [Fx.Tag.KnownXamlExternal]
145 public ObservableCollection<Type> MostRecentlyUsedTypes
147 get { return (ObservableCollection<Type>)GetValue(MostRecentlyUsedTypesProperty); }
148 set { SetValue(MostRecentlyUsedTypesProperty, value); }
151 bool isMouseLeftButtonDown = true;
153 TypeWrapper nullTypeWrapper = null;
155 public TypePresenter()
157 InitializeComponent();
159 OnBrowseTypeDirectlyChanged(this, new DependencyPropertyChangedEventArgs(
160 TypePresenter.BrowseTypeDirectlyProperty, false, this.BrowseTypeDirectly));
163 this.typeComboBox.DropDownClosed += OnTypePresenterDropDownClosed;
164 this.typeComboBox.PreviewLostKeyboardFocus += OnTypePresenterComboBoxPreviewLostKeyboardFocus;
165 this.typeComboBox.LostFocus += OnTypePresenterComboBoxLostFocus;
166 this.typeComboBox.KeyDown += OnTypePresenterKeyDown;
168 Binding textToType = new Binding();
169 textToType.Converter = new TypeWrapperConverter(this);
170 textToType.Source = this;
171 textToType.Path = new PropertyPath(TypeProperty);
172 this.typeComboBox.SetBinding(ComboBox.SelectedItemProperty, textToType);
173 this.lastSelection = (Type)TypeProperty.DefaultMetadata.DefaultValue;
175 MultiBinding automationNameBinding = new MultiBinding();
176 Binding labelBinding = new Binding("Label");
177 labelBinding.Source = this;
178 automationNameBinding.Bindings.Add(labelBinding);
179 Binding typeBinding = new Binding("Text");
180 typeBinding.Source = this.typeTextBlock;
181 automationNameBinding.Bindings.Add(typeBinding);
182 automationNameBinding.Converter = new AutomationNameConverter();
183 this.SetBinding(AutomationProperties.NameProperty, automationNameBinding);
185 this.Loaded += new RoutedEventHandler(TypePresenter_Loaded);
186 this.Unloaded += new RoutedEventHandler(TypePresenter_Unloaded);
189 void OnTypePresenterComboBoxPreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
191 if (this.typeComboBox.Visibility == Visibility.Visible && this.typeComboBox.IsDropDownOpen)
197 void OnTypePresenterComboBoxLostFocus(object sender, RoutedEventArgs e)
199 TypeWrapper tw = (TypeWrapper)this.typeComboBox.SelectedItem;
202 if (tw.Type == typeof(ArrayOf<>) || tw.Type == typeof(BrowseForType))
204 SetComboBoxToLastSelection();
209 void SetComboBoxToLastSelection()
211 if (this.lastSelection == null)
213 this.typeComboBox.SelectedIndex = this.typeComboBox.Items.IndexOf(this.NullTypeWrapper);
217 for (int i = 0; i < this.typeComboBox.Items.Count; i++)
219 TypeWrapper typeWrapper = (TypeWrapper)this.typeComboBox.Items.GetItemAt(i);
220 if (typeWrapper.IsTypeDefinition && Type.Equals(this.lastSelection, typeWrapper.Type))
222 this.typeComboBox.SelectedIndex = i;
229 public void FocusOnVisibleControl()
231 if (BrowseTypeDirectly)
233 this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (Action)(() =>
235 Keyboard.Focus(this.typeTextBlock);
240 this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (Action)(() =>
242 Keyboard.Focus(this.typeComboBox);
248 void TypePresenter_Loaded(object sender, RoutedEventArgs e)
250 //UnRegistering because of 137896: Inside tab control multiple Loaded events happen without an Unloaded event.
251 this.MostRecentlyUsedTypes.CollectionChanged -= OnMostRecentlyUsedTypesChanged;
252 this.MostRecentlyUsedTypes.CollectionChanged += OnMostRecentlyUsedTypesChanged;
253 OnMostRecentlyUsedTypesChanged(this, null);
256 void TypePresenter_Unloaded(object sender, RoutedEventArgs e)
258 this.MostRecentlyUsedTypes.CollectionChanged -= OnMostRecentlyUsedTypesChanged;
261 public event RoutedEventHandler TypeBrowserOpened
263 add { this.AddHandler(TypeBrowserOpenedEvent, value); }
264 remove { this.RemoveHandler(TypeBrowserOpenedEvent, value); }
267 public event RoutedEventHandler TypeBrowserClosed
269 add { this.AddHandler(TypeBrowserClosedEvent, value); }
270 remove { this.RemoveHandler(TypeBrowserClosedEvent, value); }
273 public event RoutedEventHandler TypeChanged
275 add { this.AddHandler(TypeChangedEvent, value); }
276 remove { this.RemoveHandler(TypeChangedEvent, value); }
279 public event PropertyChangedEventHandler PropertyChanged;
281 [Fx.Tag.KnownXamlExternal]
282 public EditingContext Context
284 get { return (EditingContext)GetValue(ContextProperty); }
285 set { SetValue(ContextProperty, value); }
288 public bool AllowNull
290 get { return (bool)GetValue(AllowNullProperty); }
291 set { SetValue(AllowNullProperty, value); }
296 get { return (string)GetValue(LabelProperty); }
297 set { SetValue(LabelProperty, value); }
300 [Fx.Tag.KnownXamlExternal]
301 public Func<Type, bool> Filter
303 get { return (Func<Type, bool>)GetValue(FilterProperty); }
304 set { SetValue(FilterProperty, value); }
307 public bool CenterActivityTypeResolverDialog
309 get { return (bool)GetValue(CenterActivityTypeResolverDialogProperty); }
310 set { SetValue(CenterActivityTypeResolverDialogProperty, value); }
313 public bool CenterTypeBrowserDialog
315 get { return (bool)GetValue(CenterTypeBrowserDialogProperty); }
316 set { SetValue(CenterTypeBrowserDialogProperty, value); }
319 internal TypeWrapper NullTypeWrapper
323 if (this.nullTypeWrapper == null)
325 this.nullTypeWrapper = new TypeWrapper(NullString, "Null", null);
327 return this.nullTypeWrapper;
333 get { return (string)GetValue(TextProperty); }
334 private set { SetValue(TextPropertyKey, value); }
338 public IEnumerable<TypeWrapper> Items
344 yield return this.NullTypeWrapper;
346 foreach (Type type in this.MostRecentlyUsedTypes)
350 if (this.Filter == null
351 || this.Filter(type))
353 yield return new TypeWrapper(type);
358 //display Array of [T] option
359 if (this.Filter == null
360 || this.Filter(typeof(Array)))
362 yield return new TypeWrapper("Array of [T]", "T[]", typeof(ArrayOf<>));
364 //display "Browse for types" option
365 //if there are referenced and local assembly info in Editing context (inside VS), type browser will show those assemblies,
366 //otherwise (standalone), type browser will just show all loaded assemblies in current appdomain
367 yield return new TypeWrapper(BrowseTypeString, "BrowseForTypes", typeof(BrowseForType));
371 public bool BrowseTypeDirectly
373 get { return (bool)GetValue(BrowseTypeDirectlyProperty); }
374 set { SetValue(BrowseTypeDirectlyProperty, value); }
378 [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "By design.")]
381 get { return (Type)GetValue(TypeProperty); }
382 set { SetValue(TypeProperty, value); }
385 public string TypeName
389 string typeName = string.Empty;
391 if (null != this.Type)
393 typeName = ResolveTypeName(this.Type);
394 this.ToolTip = typeName;
400 internal static string ResolveTypeName(Type type)
402 Fx.Assert(type != null, "parameter type is null!");
404 if (TypePresenter.DefaultTypes.Contains(type))
406 typeName = type.Name;
410 typeName = TypeNameHelper.GetDisplayName(type, true);
415 AssemblyContextControlItem AssemblyContext
419 return (null != Context ? Context.Items.GetValue<AssemblyContextControlItem>() : null);
423 string BrowseTypeString
425 get { return (string)this.FindResource("BrowseTypeString"); }
430 get { return "(null)"; }
433 protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
435 return new UIElementAutomationPeer(this);
438 protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
440 base.OnMouseLeftButtonDown(e);
441 this.isMouseLeftButtonDown = true;
445 protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
447 base.OnMouseLeftButtonUp(e);
448 if (this.isMouseLeftButtonDown)
450 if (this.BrowseTypeDirectly)
459 this.isMouseLeftButtonDown = false;
463 protected override void OnPreviewKeyDown(KeyEventArgs e)
465 base.OnPreviewKeyDown(e);
466 if (IsPreviewKey(e.Key))
472 internal static bool IsPreviewKey(Key key)
474 return (key == Key.F2 || key == Key.Space || key == Key.Enter);
477 internal void Preview()
479 if (this.BrowseTypeDirectly)
489 static void OnContextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
491 TypePresenter ctrl = (TypePresenter)sender;
492 ctrl.OnItemsChanged();
495 static void OnAllowNullChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
497 TypePresenter ctrl = (TypePresenter)sender;
498 ctrl.OnItemsChanged();
501 static void OnBrowseTypeDirectlyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
503 TypePresenter ctrl = (TypePresenter)sender;
504 if (!(bool)args.NewValue)
506 ctrl.typeComboBox.Visibility = Visibility.Visible;
507 ctrl.typeTextBlock.Visibility = Visibility.Collapsed;
508 ctrl.Focusable = false;
512 ctrl.typeComboBox.Visibility = Visibility.Collapsed;
513 ctrl.typeTextBlock.Visibility = Visibility.Visible;
514 ctrl.Focusable = true;
518 static void OnTypeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
520 TypePresenter ctrl = (TypePresenter)sender;
521 ctrl.lastSelection = (Type)args.NewValue;
523 if (null != ctrl.PropertyChanged)
525 ctrl.PropertyChanged(ctrl, new PropertyChangedEventArgs("TypeName"));
528 if (null == ctrl.lastSelection)
530 ctrl.typeComboBox.SelectedIndex = ctrl.typeComboBox.Items.IndexOf(ctrl.NullTypeWrapper);
533 ctrl.Text = ctrl.TypeName;
534 ctrl.RaiseEvent(new RoutedEventArgs(TypePresenter.TypeChangedEvent, ctrl));
537 static void OnFilterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
539 TypePresenter ctrl = (TypePresenter)sender;
540 if (null != ctrl.PropertyChanged)
542 ctrl.PropertyChanged(ctrl, new PropertyChangedEventArgs("Items"));
546 static void OnMostRecentlyUsedTypesPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
548 TypePresenter ctrl = (TypePresenter)sender;
549 ((ObservableCollection<Type>)args.NewValue).CollectionChanged += ctrl.OnMostRecentlyUsedTypesChanged;
550 ((ObservableCollection<Type>)args.OldValue).CollectionChanged -= ctrl.OnMostRecentlyUsedTypesChanged;
552 ctrl.OnItemsChanged();
555 static object OnCoerceMostRecentlyUsedTypes(DependencyObject sender, object value)
563 return TypePresenter.DefaultMostRecentlyUsedTypes;
569 if (BrowseTypeDirectly)
571 this.typeTextBlock.Visibility = Visibility.Visible;
572 this.typeComboBox.Visibility = Visibility.Collapsed;
578 if (BrowseTypeDirectly)
580 this.typeTextBlock.Visibility = Visibility.Collapsed;
581 this.typeComboBox.Visibility = Visibility.Visible;
583 this.typeComboBox.Focus();
586 // return true if KeyDownEvent should be set to handled
587 bool HandleBrowseType()
590 TypeWrapper wrapper = (TypeWrapper)this.typeComboBox.SelectedItem;
592 if ((wrapper != null && !wrapper.IsTypeDefinition)
593 || this.BrowseTypeDirectly)
596 bool? dialogResult = true;
597 bool typeIsArray = true;
598 bool fireEvent = false;
599 //handle choosing an array of T
600 if (wrapper != null && typeof(ArrayOf<>) == wrapper.Type)
603 this.RaiseEvent(new RoutedEventArgs(TypePresenter.TypeBrowserOpenedEvent, this));
604 result = wrapper.Type;
606 else if (wrapper != null && wrapper.DisplayName == NullString)
615 this.RaiseEvent(new RoutedEventArgs(TypePresenter.TypeBrowserOpenedEvent, this));
616 TypeBrowser browser = new TypeBrowser(AssemblyContext, this.Context, this.Filter);
617 SetWindowOwner(browser);
618 if (this.CenterTypeBrowserDialog)
620 browser.WindowStartupLocation = WindowStartupLocation.CenterScreen;
622 dialogResult = browser.ShowDialog();
623 if (dialogResult.HasValue && dialogResult.Value)
625 result = browser.ConcreteType;
631 if (dialogResult.HasValue && dialogResult.Value)
633 //user may have chosen generic type (IList)
634 if (result.IsGenericTypeDefinition)
637 ActivityTypeResolver wnd = new ActivityTypeResolver();
639 wnd.Context = this.Context;
640 wnd.EditedType = result;
641 if (this.CenterActivityTypeResolverDialog)
643 wnd.WindowStartupLocation = WindowStartupLocation.CenterScreen;
646 result = (true == wnd.ShowDialog() ? wnd.ConcreteType : null);
651 //if we have a ArrayOf<some type here>, create actual array type
654 result = result.GetGenericArguments()[0].MakeArrayType();
656 //add it to the cache
657 if (!MostRecentlyUsedTypes.Any<Type>(p => Type.Equals(p, result)))
659 MostRecentlyUsedTypes.Add(result);
662 //and return updated result
667 this.Type = this.lastSelection;
670 BindingExpression binding = this.typeComboBox.GetBindingExpression(ComboBox.SelectedItemProperty);
671 binding.UpdateTarget();
675 SetComboBoxToLastSelection();
679 this.RaiseEvent(new RoutedEventArgs(TypePresenter.TypeBrowserClosedEvent, this));
686 void OnMostRecentlyUsedTypesChanged(object sender, NotifyCollectionChangedEventArgs e)
691 void OnItemsChanged()
693 if (null != PropertyChanged)
695 PropertyChanged(this, new PropertyChangedEventArgs("Items"));
699 void OnTypePresenterDropDownClosed(object sender, EventArgs e)
703 if (!this.BrowseTypeDirectly)
705 this.typeComboBox.Focus();
713 void OnTypePresenterKeyDown(object sender, KeyEventArgs e)
715 if (e.Key == Key.Enter)
717 if (HandleBrowseType())
723 FocusOnVisibleControl();
727 void OnTypePresenterLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
729 if (!(e.NewFocus == this))
731 if (!(this.typeComboBox.IsDropDownOpen || this.typeComboBox.IsSelectionBoxHighlighted))
738 void SetWindowOwner(Window wnd)
740 WindowHelperService.TrySetWindowOwner(this, this.Context, wnd);
743 // internal converter class - assign a meaningful AutomationProperties.Name to the type presenter
744 // AutomationProperties.Name = Label + the string displayed on the TypePresenter
745 sealed class AutomationNameConverter : IMultiValueConverter
747 public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
749 Fx.Assert(values.Length == 2, "There should be exactly 2 values");
750 return (string)values[0] + ": " + (string)values[1];
753 public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
755 Fx.Assert("Not supported!");
761 [Fx.Tag.XamlVisible(false)]
762 public sealed class TypeWrapper
765 bool isTypeDefinition;
768 internal TypeWrapper(Type type)
771 this.isTypeDefinition = true;
772 this.Tag = DisplayName;
775 internal TypeWrapper(string text, string tag, Type type)
777 this.displayName = text;
778 this.isTypeDefinition = false;
783 public string DisplayName
787 if (this.isTypeDefinition)
789 if (TypePresenter.DefaultTypes.Contains(this.type))
791 return this.type.Name;
794 return TypeNameHelper.GetDisplayName(this.Type, true);
796 return this.displayName;
800 public bool IsTypeDefinition
802 get { return this.isTypeDefinition; }
811 [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "By design.")]
814 get { return this.type; }
817 public override string ToString()
819 return Tag as string;
822 public override bool Equals(object obj)
824 TypeWrapper that = obj as TypeWrapper;
829 if (that.IsTypeDefinition ^ this.IsTypeDefinition)
833 if (this.displayName != that.displayName)
837 return object.Equals(this.Type, that.Type);
840 public override int GetHashCode()
842 if (this.Type != null)
844 return this.Type.GetHashCode();
848 return base.GetHashCode();
853 sealed class ArrayOf<T>
857 sealed class BrowseForType
861 // internal converter class - keeps link between display friendly string representation of types
862 // and actual underlying system type.
863 sealed class TypeWrapperConverter : IValueConverter
865 TypePresenter typePresenter;
867 //ctor - initialzied with list of loaded types into the presenter
868 internal TypeWrapperConverter(TypePresenter typePresenter)
870 this.typePresenter = typePresenter;
873 //convert from System.Type to TypeWrapper (display friendly)
874 public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
878 //lookup in loaded types if type is already there
880 //if no - add it to collection - may be reused later
881 if (null == this.typePresenter.MostRecentlyUsedTypes.SingleOrDefault<Type>(p => Type.Equals(p, (Type)value)))
883 this.typePresenter.MostRecentlyUsedTypes.Add((Type)value);
886 return new TypeWrapper((Type)value);
890 return this.typePresenter.NullTypeWrapper;
894 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
896 //convert back - just get the Type property of the wrapper object
897 TypeWrapper typeWrapper = value as TypeWrapper;
899 if (typeWrapper == this.typePresenter.NullTypeWrapper)
904 if (null != typeWrapper && null != typeWrapper.Type && typeof(ArrayOf<>) != typeWrapper.Type && typeof(BrowseForType) != typeWrapper.Type)
906 return typeWrapper.Type;
909 return Binding.DoNothing;