添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
//--------------------------------------------------------------------------- //--------------------------------------------------------------------------- using System; using System.ComponentModel; using System.Collections; using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using System.Windows.Threading; using System.Windows; using System.Windows.Automation.Peers; using System.Security; using System.Security.Permissions; using System.Windows.Media; using System.Windows.Input; using System.Windows.Documents; using System.Windows.Interop; using System.Windows.Controls.Primitives; using System.Windows.Markup; using System.Windows.Shapes; using MS.Internal.KnownBoxes; using MS.Internal; using MS.Internal.Telemetry.PresentationFramework; namespace System.Windows.Controls /// < summary > /// ComboBox control /// </ summary > [ Localizability (LocalizationCategory.ComboBox)] [ TemplatePart ( Name = "PART_EditableTextBox" , Type = typeof ( TextBox ))] [ TemplatePart ( Name = "PART_Popup" , Type = typeof ( Popup ))] [ StyleTypedProperty ( Property = "ItemContainerStyle" , StyleTargetType = typeof ( ComboBoxItem ))] public class ComboBox : Selector # region Constructors static ComboBox () KeyboardNavigation . TabNavigationProperty . OverrideMetadata ( typeof ( ComboBox ), new FrameworkPropertyMetadata ( KeyboardNavigationMode . Local )); KeyboardNavigation . ControlTabNavigationProperty . OverrideMetadata ( typeof ( ComboBox ), new FrameworkPropertyMetadata ( KeyboardNavigationMode . None )); KeyboardNavigation . DirectionalNavigationProperty . OverrideMetadata ( typeof ( ComboBox ), new FrameworkPropertyMetadata ( KeyboardNavigationMode . None )); // Disable tooltips on combo box when it is open ToolTipService . IsEnabledProperty . OverrideMetadata ( typeof ( ComboBox ), new FrameworkPropertyMetadata ( null , new CoerceValueCallback ( CoerceToolTipIsEnabled ))); DefaultStyleKeyProperty . OverrideMetadata ( typeof ( ComboBox ), new FrameworkPropertyMetadata ( typeof ( ComboBox ))); _dType = DependencyObjectType .FromSystemTypeInternal( typeof ( ComboBox )); IsTextSearchEnabledProperty . OverrideMetadata ( typeof ( ComboBox ), new FrameworkPropertyMetadata (BooleanBoxes.TrueBox)); EventManager . RegisterClassHandler ( typeof ( ComboBox ), Mouse . LostMouseCaptureEvent , new MouseEventHandler ( OnLostMouseCapture )); EventManager . RegisterClassHandler ( typeof ( ComboBox ), Mouse . MouseDownEvent , new MouseButtonEventHandler ( OnMouseButtonDown ), true ); // call us even if the transparent button in the style gets the click. EventManager . RegisterClassHandler ( typeof ( ComboBox ), Mouse . MouseMoveEvent , new MouseEventHandler ( OnMouseMove )); EventManager . RegisterClassHandler ( typeof ( ComboBox ), Mouse . PreviewMouseDownEvent , new MouseButtonEventHandler ( OnPreviewMouseButtonDown )); EventManager . RegisterClassHandler ( typeof ( ComboBox ), Mouse . MouseWheelEvent , new MouseWheelEventHandler ( OnMouseWheel ), true ); // call us even if textbox in the style gets the click. EventManager . RegisterClassHandler ( typeof ( ComboBox ), UIElement . GotFocusEvent , new RoutedEventHandler ( OnGotFocus )); // call us even if textbox in the style get focus // Listen for ContextMenu openings/closings EventManager . RegisterClassHandler ( typeof ( ComboBox ), ContextMenuService . ContextMenuOpeningEvent , new ContextMenuEventHandler ( OnContextMenuOpen ), true ); EventManager . RegisterClassHandler ( typeof ( ComboBox ), ContextMenuService . ContextMenuClosingEvent , new ContextMenuEventHandler ( OnContextMenuClose ), true ); IsEnabledProperty . OverrideMetadata ( typeof ( ComboBox ), new UIPropertyMetadata ( new PropertyChangedCallback ( OnVisualStatePropertyChanged ))); IsMouseOverPropertyKey. OverrideMetadata ( typeof ( ComboBox ), new UIPropertyMetadata ( new PropertyChangedCallback ( OnVisualStatePropertyChanged ))); IsSelectionActivePropertyKey . OverrideMetadata ( typeof ( ComboBox ), new FrameworkPropertyMetadata ( new PropertyChangedCallback ( OnVisualStatePropertyChanged ))); ControlsTraceLogger.AddControl(TelemetryControls.ComboBox); /// < summary > /// Default DependencyObject constructor /// </ summary > public ComboBox () : base () Initialize (); # endregion # region Properties /// < summary > /// DependencyProperty for MaxDropDownHeight /// </ summary > public static readonly DependencyProperty MaxDropDownHeightProperty = DependencyProperty . Register ( "MaxDropDownHeight" , typeof ( double ), typeof ( ComboBox ), new FrameworkPropertyMetadata ( SystemParameters . PrimaryScreenHeight / 3, OnVisualStatePropertyChanged )); /// < summary > /// The maximum height of the popup /// </ summary > [ Bindable ( true ), Category ( "Layout" )] [ TypeConverter ( typeof ( LengthConverter ))] public double MaxDropDownHeight return ( double ) GetValue ( MaxDropDownHeightProperty ); SetValue ( MaxDropDownHeightProperty , value ); /// < summary > /// DependencyProperty for IsDropDownOpen /// </ summary > public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty . Register ( "IsDropDownOpen" , typeof ( bool ), typeof ( ComboBox ), new FrameworkPropertyMetadata ( BooleanBoxes.FalseBox, FrameworkPropertyMetadataOptions . BindsTwoWayByDefault , new PropertyChangedCallback ( OnIsDropDownOpenChanged ), new CoerceValueCallback ( CoerceIsDropDownOpen ))); /// < summary > /// Whether or not the "popup" for this control is currently open /// </ summary > [ Bindable ( true ), Browsable ( false ), Category ( "Appearance" )] public bool IsDropDownOpen get { return ( bool ) GetValue ( IsDropDownOpenProperty ); } set { SetValue ( IsDropDownOpenProperty , BooleanBoxes.Box( value )); } /// < summary > /// DependencyProperty for ShouldPreserveUserEnteredPrefix. /// </ summary > public static readonly DependencyProperty ShouldPreserveUserEnteredPrefixProperty = DependencyProperty . Register ( "ShouldPreserveUserEnteredPrefix" , typeof ( bool ), typeof ( ComboBox ), new FrameworkPropertyMetadata (BooleanBoxes.FalseBox)); /// < summary > /// Whether or not the user entered prefix should be preserved in auto lookup matched text. /// </ summary > public bool ShouldPreserveUserEnteredPrefix get { return ( bool ) GetValue ( ShouldPreserveUserEnteredPrefixProperty ); } set { SetValue ( ShouldPreserveUserEnteredPrefixProperty , BooleanBoxes.Box( value )); } private static object CoerceIsDropDownOpen ( DependencyObject d , object value ) if (( bool ) value ) ComboBox cb = ( ComboBox ) d ; if (! cb . IsLoaded ) cb . RegisterToOpenOnLoad (); return BooleanBoxes.FalseBox; return value ; private static object CoerceToolTipIsEnabled ( DependencyObject d , object value ) ComboBox cb = ( ComboBox ) d ; return cb . IsDropDownOpen ? BooleanBoxes.FalseBox : value ; private void RegisterToOpenOnLoad () Loaded += new RoutedEventHandler ( OpenOnLoad ); private void OpenOnLoad ( object sender , RoutedEventArgs e ) // Open combobox after it has rendered (Loaded is fired before 1st render) Dispatcher . BeginInvoke ( DispatcherPriority . Input , new DispatcherOperationCallback ( delegate ( object param ) CoerceValue ( IsDropDownOpenProperty ); return null ; }), null ); /// < summary > /// </ summary > /// < param name = " e " > </ param > protected virtual void OnDropDownOpened ( EventArgs e ) RaiseClrEvent ( DropDownOpenedKey , e ); /// < summary > /// </ summary > /// < param name = " e " > </ param > protected virtual void OnDropDownClosed ( EventArgs e ) RaiseClrEvent ( DropDownClosedKey , e ); private static void OnIsDropDownOpenChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e ) ComboBox comboBox = ( ComboBox ) d ; comboBox . HasMouseEnteredItemsHost = false ; bool newValue = ( bool ) e . NewValue ; bool oldValue = ! newValue ; // Fire accessibility event ComboBoxAutomationPeer peer = UIElementAutomationPeer . FromElement ( comboBox ) as ComboBoxAutomationPeer ; if ( peer != null ) peer . RaiseExpandCollapseAutomationEvent ( oldValue , newValue ); if ( newValue ) // When the drop down opens, take capture Mouse . Capture ( comboBox , CaptureMode . SubTree ); // Select text if editable if ( comboBox . IsEditable && comboBox . EditableTextBoxSite != null ) comboBox . EditableTextBoxSite . SelectAll (); if ( comboBox . _clonedElement != null && VisualTreeHelper . GetParent ( comboBox . _clonedElement ) == null ) comboBox . Dispatcher . BeginInvoke ( DispatcherPriority . Loaded , ( DispatcherOperationCallback ) delegate ( object arg ) ComboBox cb = ( ComboBox ) arg ; cb . UpdateSelectionBoxItem (); if ( cb . _clonedElement != null ) cb . _clonedElement . CoerceValue ( FrameworkElement . FlowDirectionProperty ); return null ; comboBox ); // Popup.IsOpen is databound to IsDropDownOpen. We can't know // if IsDropDownOpen will be invalidated before Popup.IsOpen. // If we are invalidated first and we try to focus the item, we // might succeed (b/c there's a logical link from the item to // a PresentationSource). When the popup finally opens, Focus // will be sent to null because Core doesn't know what else to do. // So, we must focus the element only after we are sure the popup // has opened. We will queue an operation (at Send priority) to // do this work -- this is the soonest we can make this happen. comboBox . Dispatcher . BeginInvoke ( DispatcherPriority . Send , ( DispatcherOperationCallback ) delegate ( object arg ) ComboBox cb = ( ComboBox ) arg ; if ( cb . IsItemsHostVisible ) cb . NavigateToItem ( cb . InternalSelectedInfo , ItemNavigateArgs . Empty , true /* alwaysAtTopOfViewport */ ); return null ; comboBox ); comboBox . OnDropDownOpened ( EventArgs . Empty ); // If focus is within the subtree, make sure we have the focus so that focus isn't in the disposed hwnd if ( comboBox . IsKeyboardFocusWithin ) if ( comboBox . IsEditable ) if ( comboBox . EditableTextBoxSite != null && ! comboBox . EditableTextBoxSite . IsKeyboardFocusWithin ) comboBox . Focus (); // It's not editable, make sure the combobox has focus comboBox . Focus (); // Make sure to clear the highlight when the dropdown closes comboBox . HighlightedInfo = null ; if ( comboBox . HasCapture ) Mouse . Capture ( null ); // No Popup in the style so fire closed now if ( comboBox . _dropDownPopup == null ) comboBox . OnDropDownClosed ( EventArgs . Empty ); comboBox . CoerceValue ( IsSelectionBoxHighlightedProperty ); comboBox . CoerceValue ( ToolTipService . IsEnabledProperty ); comboBox . UpdateVisualState (); private void OnPopupClosed ( object source , EventArgs e ) OnDropDownClosed ( EventArgs . Empty ); /// < summary > /// DependencyProperty for IsEditable /// </ summary > public static readonly DependencyProperty IsEditableProperty = DependencyProperty . Register ( "IsEditable" , typeof ( bool ), typeof ( ComboBox ), new FrameworkPropertyMetadata ( BooleanBoxes.FalseBox, new PropertyChangedCallback ( OnIsEditableChanged ))); /// < summary > /// True if this ComboBox is editable. /// </ summary > /// < value > </ value > public bool IsEditable get { return ( bool ) GetValue ( IsEditableProperty ); } set { SetValue ( IsEditableProperty , BooleanBoxes.Box( value )); } private static void OnIsEditableChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e ) ComboBox cb = d as ComboBox ; cb . Update (); cb . UpdateVisualState (); /// < summary > /// DependencyProperty for Text /// </ summary > public static readonly DependencyProperty TextProperty = DependencyProperty . Register ( "Text" , typeof ( string ), typeof ( ComboBox ), new FrameworkPropertyMetadata ( String . Empty , FrameworkPropertyMetadataOptions . BindsTwoWayByDefault | FrameworkPropertyMetadataOptions . Journal , new PropertyChangedCallback ( OnTextChanged ))); /// < summary > /// The text of the currently selected item. When there is no SelectedItem and IsEditable is true /// this is the text entered in the text box. When IsEditable is false, this value represent the string version of the selected item. /// </ summary > /// < value > </ value > public string Text get { return ( string ) GetValue ( TextProperty ); } set { SetValue ( TextProperty , value ); } /// < summary > /// DependencyProperty for the IsReadOnlyProperty /// </ summary > public static readonly DependencyProperty IsReadOnlyProperty = TextBox . IsReadOnlyProperty . AddOwner ( typeof ( ComboBox )); /// < summary > /// When the ComboBox is Editable, if the TextBox within it is read only. /// </ summary > public bool IsReadOnly get { return ( bool ) GetValue ( IsReadOnlyProperty ); } set { SetValue ( IsReadOnlyProperty , BooleanBoxes.Box( value )); } private static readonly DependencyPropertyKey SelectionBoxItemPropertyKey = DependencyProperty . RegisterReadOnly ( "SelectionBoxItem" , typeof ( object ), typeof ( ComboBox ), new FrameworkPropertyMetadata ( String . Empty )); // This property is used as a Style Helper. // When the SelectedItem is a UIElement a VisualBrush is created and set to the Fill property // of a Rectangle. Then we set SelectionBoxItem to that rectangle. // For data items, SelectionBoxItem is set to a string. /// < summary > /// The DependencyProperty for the SelectionBoxItemProperty /// </ summary > public static readonly DependencyProperty SelectionBoxItemProperty = SelectionBoxItemPropertyKey . DependencyProperty ; /// < summary > /// Used to display the selected item /// </ summary > public object SelectionBoxItem get { return GetValue ( SelectionBoxItemProperty ); } private set { SetValue ( SelectionBoxItemPropertyKey , value ); } private static readonly DependencyPropertyKey SelectionBoxItemTemplatePropertyKey = DependencyProperty . RegisterReadOnly ( "SelectionBoxItemTemplate" , typeof ( DataTemplate ), typeof ( ComboBox ), new FrameworkPropertyMetadata (( DataTemplate ) null )); /// < summary > /// The DependencyProperty for the SelectionBoxItemProperty /// </ summary > public static readonly DependencyProperty SelectionBoxItemTemplateProperty = SelectionBoxItemTemplatePropertyKey . DependencyProperty ; /// < summary > /// Used to set the item DataTemplate /// </ summary > public DataTemplate SelectionBoxItemTemplate get { return ( DataTemplate ) GetValue ( SelectionBoxItemTemplateProperty ); } private set { SetValue ( SelectionBoxItemTemplatePropertyKey , value ); } private static readonly DependencyPropertyKey SelectionBoxItemStringFormatPropertyKey = DependencyProperty . RegisterReadOnly ( "SelectionBoxItemStringFormat" , typeof ( String ), typeof ( ComboBox ), new FrameworkPropertyMetadata (( String ) null )); /// < summary > /// The DependencyProperty for the SelectionBoxItemProperty /// </ summary > public static readonly DependencyProperty SelectionBoxItemStringFormatProperty = SelectionBoxItemStringFormatPropertyKey . DependencyProperty ; /// < summary > /// Used to set the item DataStringFormat /// </ summary > public String SelectionBoxItemStringFormat get { return ( String ) GetValue ( SelectionBoxItemStringFormatProperty ); } private set { SetValue ( SelectionBoxItemStringFormatPropertyKey , value ); } /// < summary > /// DependencyProperty for StaysOpenOnEdit /// </ summary > public static readonly DependencyProperty StaysOpenOnEditProperty = DependencyProperty . Register ( "StaysOpenOnEdit" , typeof ( bool ), typeof ( ComboBox ), new FrameworkPropertyMetadata (BooleanBoxes.FalseBox)); /// < summary > /// Determines whether the ComboBox will remain open when clicking on /// the text box when the drop down is open /// </ summary > /// < value > </ value > public bool StaysOpenOnEdit return ( bool ) GetValue ( StaysOpenOnEditProperty ); SetValue ( StaysOpenOnEditProperty , BooleanBoxes.Box( value )); private static readonly DependencyPropertyKey IsSelectionBoxHighlightedPropertyKey = DependencyProperty . RegisterReadOnly ( "IsSelectionBoxHighlighted" , typeof ( bool ), typeof ( ComboBox ), new FrameworkPropertyMetadata (BooleanBoxes.FalseBox, null , new CoerceValueCallback ( CoerceIsSelectionBoxHighlighted ))); /// < summary > /// The DependencyProperty for the IsSelectionBoxHighlighted Property /// </ summary > private static readonly DependencyProperty IsSelectionBoxHighlightedProperty = IsSelectionBoxHighlightedPropertyKey . DependencyProperty ; /// < summary > /// Indicates the SelectionBox area should be highlighted /// </ summary > public bool IsSelectionBoxHighlighted get { return ( bool ) GetValue ( IsSelectionBoxHighlightedProperty ); } private static object CoerceIsSelectionBoxHighlighted ( object o , object value ) ComboBox comboBox = ( ComboBox ) o ; ComboBoxItem highlightedElement ; return (! comboBox . IsDropDownOpen && comboBox . IsKeyboardFocusWithin ) || (( highlightedElement = comboBox . HighlightedElement ) != null && highlightedElement . Content == comboBox . _clonedElement ); # endregion # region Events /// < summary > /// DropDown Open event /// </ summary > public event EventHandler DropDownOpened add { EventHandlersStoreAdd ( DropDownOpenedKey , value ); } remove { EventHandlersStoreRemove ( DropDownOpenedKey , value ); } private static readonly EventPrivateKey DropDownOpenedKey = new EventPrivateKey (); /// < summary > /// DropDown Close event /// </ summary > public event EventHandler DropDownClosed add { EventHandlersStoreAdd ( DropDownClosedKey , value ); } remove { EventHandlersStoreRemove ( DropDownClosedKey , value ); } private static readonly EventPrivateKey DropDownClosedKey = new EventPrivateKey (); # endregion # region Selection Changed // Combo Box has several methods of input for selecting items // * Selector.OnSelectionChanged // * ComboBox.Text Changed // * Editable Text Box changed // When any one of these inputs change, the other two must be updated to reflect // the third. // When Text changes, TextUpdated() tries searching (if TextSearch is enabled) for an // item that exactly matches the new value of Text. If it finds one, it sets // selected index to that item. This will cause OnSelectionChanged to update // the SelectionBoxItem. Finally TextUpdated() updates the TextBox. // When the TextBox text changes, TextUpdated() tries searching (if TextSearch is enabled) // for an item that partially matches the new value of Text. If it finds one, it updates // The textbox and selects the remaining portion of text. Then it sets the selected index // which will cause OnSelectionChanged to update the SelectionBoxItem. Finally // TextUpdated() updates ComboBox.Text property. // When Selection changes, SelectedItemUpdated() will update the ComboBox.Text property // and then update the SelectionBoxItem or EditableTextBox.Text depending on edit mode /// < summary > /// A virtual function that is called when the selection is changed. Default behavior /// is to raise a SelectionChangedEvent /// </ summary > /// < param name = " e " > The inputs for this event. Can be raised (default behavior) or processed /// in some other way. </ param > protected override void OnSelectionChanged ( SelectionChangedEventArgs e ) base . OnSelectionChanged ( e ); SelectedItemUpdated (); if ( IsDropDownOpen ) ItemInfo selectedInfo = InternalSelectedInfo ; if ( selectedInfo != null ) NavigateToItem ( selectedInfo , ItemNavigateArgs . Empty ); if ( AutomationPeer . ListenerExists ( AutomationEvents . SelectionPatternOnInvalidated ) || AutomationPeer . ListenerExists ( AutomationEvents . SelectionItemPatternOnElementSelected ) || AutomationPeer . ListenerExists ( AutomationEvents . SelectionItemPatternOnElementAddedToSelection ) || AutomationPeer . ListenerExists ( AutomationEvents . SelectionItemPatternOnElementRemovedFromSelection ) ) ComboBoxAutomationPeer peer = UIElementAutomationPeer . CreatePeerForElement ( this ) as ComboBoxAutomationPeer ; if ( peer != null ) peer . RaiseSelectionEvents ( e ); // When the selected item (or its content) changes, update // The SelectedItem property and the Text properties // ComboBoxItem also calls this method when its content changes internal void SelectedItemUpdated () UpdatingSelectedItem = true ; // If the selection changed as a result of Text or the TextBox // changing, don't update the Text property - TextUpdated() will if (! UpdatingText ) // Don't change the text in the TextBox unless there is an item selected. string text = TextSearch . GetPrimaryTextFromItem ( this , InternalSelectedItem ); if ( Text != text ) SetCurrentValueInternal( TextProperty , text ); // Update SelectionItem/TextBox Update (); finally UpdatingSelectedItem = false ; // When the Text Property changes, search for an item exactly // matching the new text and set the selected index to that item private static void OnTextChanged ( DependencyObject d , DependencyPropertyChangedEventArgs e ) ComboBox cb = ( ComboBox ) d ; ComboBoxAutomationPeer peer = UIElementAutomationPeer . FromElement ( cb ) as ComboBoxAutomationPeer ; // Raise the propetyChangeEvent for Value if Automation Peer exist, the new Value must // be the one in SelctionBoxItem(selected value is the one user will care about) if ( peer != null ) peer . RaiseValuePropertyChangedEvent (( string ) e . OldValue , ( string ) e . NewValue ); cb . TextUpdated (( string ) e . NewValue , false ); // When the user types in the TextBox, search for an item that partially // matches the new text and set the selected index to that item private void OnEditableTextBoxTextChanged ( object sender , TextChangedEventArgs e ) Debug . Assert ( _editableTextBoxSite == sender ); if (! IsEditable ) // Don't do any work if we're not editable. return ; TextUpdated ( EditableTextBoxSite . Text , true ); // When selection changes, save the location of the selection start // (ignoring changes during compositions) private void OnEditableTextBoxSelectionChanged ( object sender , RoutedEventArgs e ) if (! Helper . IsComposing ( EditableTextBoxSite )) _textBoxSelectionStart = EditableTextBoxSite . SelectionStart ; // When the IME composition we're waiting for completes, run the text search logic private void OnEditableTextBoxPreviewTextInput ( object sender , System.Windows.Input. TextCompositionEventArgs e ) if ( IsWaitingForTextComposition && e . TextComposition .Source == EditableTextBoxSite && e . TextComposition .Stage == System.Windows.Input.TextCompositionStage.Done) IsWaitingForTextComposition = false ; TextUpdated ( EditableTextBoxSite . Text , true ); // ComboBox.Text has just changed, but EditableTextBoxSite.Text hasn't. // As a courtesy to apps and controls that expect a TextBox.TextChanged // event after ComboTox.Text changes, raise such an event now. // (A notable example is TFS's WpfFieldControl - see Dev11 964048) EditableTextBoxSite . RaiseCourtesyTextChangedEvent (); // If TextSearch is enabled search for an item matching the new text // (partial search if user is typing, exact search if setting Text) private void TextUpdated ( string newText , bool textBoxUpdated ) // Only process this event if it is coming from someone outside setting Text directly if (! UpdatingText && ! UpdatingSelectedItem ) // if a composition is in progress, wait for it to complete if ( Helper . IsComposing ( EditableTextBoxSite )) IsWaitingForTextComposition = true ; return ; // Set the updating flags so we don't reenter this function UpdatingText = true ; // Try searching for an item matching the new text if ( IsTextSearchEnabled ) if ( _updateTextBoxOperation != null ) // cancel any pending async update of the textbox _updateTextBoxOperation . Abort (); _updateTextBoxOperation = null ; MatchedTextInfo matchedTextInfo = TextSearch . FindMatchingPrefix ( this , newText ); int matchedIndex = matchedTextInfo .MatchedItemIndex; if ( matchedIndex >= 0) // Allow partial matches when updating textbox if ( textBoxUpdated ) int selectionStart = EditableTextBoxSite . SelectionStart ; // Perform type search when the selection is at the end // of the textbox and the selection start increased if ( selectionStart == newText . Length && selectionStart > _textBoxSelectionStart ) // Replace the currently typed text with the text // from the matched item string matchedText = matchedTextInfo .MatchedText; if ( ShouldPreserveUserEnteredPrefix ) // Retain the user entered prefix in the matched text. matchedText = String . Concat ( newText , matchedText .Substring( matchedTextInfo .MatchedPrefixLength)); // If there's an IME, do the replacement asynchronously so that // it doesn't get confused with the IME's undo stack. MS.Internal.Documents. UndoManager undoManager = EditableTextBoxSite . TextContainer . UndoManager ; if ( undoManager != null && undoManager . OpenedUnit != null && undoManager . OpenedUnit . GetType () != typeof ( TextParentUndoUnit )) _updateTextBoxOperation = Dispatcher . BeginInvoke ( DispatcherPriority . Normal , new DispatcherOperationCallback ( UpdateTextBoxCallback ), new object [] { matchedText , matchedTextInfo }); // when there's no IME, do it synchronously UpdateTextBox ( matchedText , matchedTextInfo ); // ComboBox's text property should be updated with the matched text newText = matchedText ; else //Text Property Set // Require exact matches when setting TextProperty string matchedText = matchedTextInfo .MatchedText; if (! String . Equals ( newText , matchedText , StringComparison . CurrentCulture )) // Strings not identical, no match matchedIndex = -1; // Update SelectedIndex if it changed if ( matchedIndex != SelectedIndex ) // OnSelectionChanged will update the SelectedItem SetCurrentValueInternal( SelectedIndexProperty , matchedIndex ); // Update TextProperty when TextBox changes and TextBox when TextProperty changes if ( textBoxUpdated ) SetCurrentValueInternal( TextProperty , newText ); else if ( EditableTextBoxSite != null ) EditableTextBoxSite . Text = newText ; finally // Clear the updating flag UpdatingText = false ; object UpdateTextBoxCallback ( object arg ) _updateTextBoxOperation = null ; object [] args = ( object []) arg ; string matchedText = ( string ) args [0]; MatchedTextInfo matchedTextInfo = (MatchedTextInfo) args [1]; UpdatingText = true ; UpdateTextBox ( matchedText , matchedTextInfo ); finally UpdatingText = false ; return null ; void UpdateTextBox ( string matchedText , MatchedTextInfo matchedTextInfo ) // Replace the TextBox's text with the matched text and // select the text beyond what the user typed EditableTextBoxSite . Text = matchedText ; EditableTextBoxSite . SelectionStart = matchedText . Length - matchedTextInfo .TextExcludingPrefixLength; EditableTextBoxSite . SelectionLength = matchedTextInfo .TextExcludingPrefixLength; // Updates: // SelectionBox if not editable // EditableTextBox.Text if editable private void Update () if ( IsEditable ) UpdateEditableTextBox (); UpdateSelectionBoxItem (); // Update the editable TextBox to match combobox text private void UpdateEditableTextBox () if (! UpdatingText ) UpdatingText = true ; string text = Text ; // Copy ComboBox.Text to the editable TextBox if ( EditableTextBoxSite != null && EditableTextBoxSite . Text != text ) EditableTextBoxSite . Text = text ; EditableTextBoxSite . SelectAll (); finally UpdatingText = false ; /// < summary > /// This function updates the selected item in the "selection box". /// This is called when selection changes or when the combobox /// switches from editable to non-editable or vice versa. /// This will also get called in ApplyTemplate in case selection /// is set prior to the control being measured. /// </ summary > private void UpdateSelectionBoxItem () // propagate the new selected item to the SelectionBoxItem property; // this displays it in the selection box object item = InternalSelectedItem ; DataTemplate itemTemplate = ItemTemplate ; string stringFormat = ItemStringFormat ; // if Items contains an explicit ContentControl, use its content instead // (this handles the case of ComboBoxItem) ContentControl contentControl = item as ContentControl ; if ( contentControl != null ) item = contentControl . Content ; itemTemplate = contentControl . ContentTemplate ; stringFormat = contentControl . ContentStringFormat ; if ( _clonedElement != null ) _clonedElement . LayoutUpdated -= CloneLayoutUpdated ; _clonedElement = null ; if ( itemTemplate == null && ItemTemplateSelector == null && stringFormat == null ) // if the item is a logical element it cannot be displayed directly in // the selection box because it already belongs to the tree (in the dropdown box). // Instead, try to extract some useful text from the visual. DependencyObject logicalElement = item as DependencyObject ; if ( logicalElement != null ) // If the item is a UIElement, create a copy using a visual brush _clonedElement = logicalElement as UIElement ; if ( _clonedElement != null ) // Create visual copy of selected element VisualBrush visualBrush = new VisualBrush ( _clonedElement ); visualBrush . Stretch = Stretch . None ; //Set position and dimension of content visualBrush . ViewboxUnits = BrushMappingMode . Absolute ; visualBrush . Viewbox = new Rect ( _clonedElement . RenderSize ); //Set position and dimension of tile visualBrush . ViewportUnits = BrushMappingMode . Absolute ; visualBrush . Viewport = new Rect ( _clonedElement . RenderSize ); // We need to check if the item acquires a mirror transform through the visual tree // below the ComboBox. If it does then the same mirror transform needs to be applied // to the VisualBrush so that the item shows identically with the selection box as it does // within the dropdown. Eg. // ComboBox - LTR // | \ // | ComboBoxItem-RTL // | / // TextBlock (item) - LTR // This TextBlock (item) will need to be mirrored through the VisualBrush, to appear the // same as it does through the ComboBoxItem's mirror transform. DependencyObject parent = VisualTreeHelper . GetParent ( _clonedElement ); FlowDirection parentFD = parent == null ? FlowDirection . LeftToRight : ( FlowDirection ) parent . GetValue ( FlowDirectionProperty ); if ( this . FlowDirection != parentFD ) visualBrush . Transform = new MatrixTransform ( new Matrix (-1.0, 0.0, 0.0, 1.0, _clonedElement . RenderSize . Width , 0.0)); // Apply visual brush to a rectangle Rectangle rect = new Rectangle (); rect . Fill = visualBrush ; rect . Width = _clonedElement . RenderSize . Width ; rect . Height = _clonedElement . RenderSize . Height ; _clonedElement . LayoutUpdated += CloneLayoutUpdated ; item = rect ; itemTemplate = null ; item = ExtractString ( logicalElement ); itemTemplate = ContentPresenter . StringContentTemplate ; // display a null item by an empty string if ( item == null ) item = String . Empty ; itemTemplate = ContentPresenter . StringContentTemplate ; SelectionBoxItem = item ; SelectionBoxItemTemplate = itemTemplate ; SelectionBoxItemStringFormat = stringFormat ; // Update our clone's size to match the actual object's size private void CloneLayoutUpdated ( object sender , EventArgs e ) Rectangle rect = ( Rectangle ) SelectionBoxItem ; rect . Width = _clonedElement . RenderSize . Width ; rect . Height = _clonedElement . RenderSize . Height ; VisualBrush visualBrush = ( VisualBrush ) rect . Fill ; visualBrush . Viewbox = new Rect ( _clonedElement . RenderSize ); visualBrush . Viewport = new Rect ( _clonedElement . RenderSize ); # endregion # region Protected Methods /// < summary > /// Change to the correct visual state. /// </ summary > /// < param name = " useTransitions " > /// true to use transitions when updating the visual state, false to snap directly to the new visual state. /// </ param > internal override void ChangeVisualState ( bool useTransitions ) // Common States Group if (! IsEnabled ) VisualStateManager . GoToState ( this , VisualStates . StateDisabled , useTransitions ); else if ( IsMouseOver ) VisualStateManager . GoToState ( this , VisualStates . StateMouseOver , useTransitions ); VisualStateManager . GoToState ( this , VisualStates . StateNormal , useTransitions ); // Focus States Group if (! GetIsSelectionActive ( this )) VisualStateManager . GoToState ( this , VisualStates . StateUnfocused , useTransitions ); else if ( IsDropDownOpen ) VisualStateManager . GoToState ( this , VisualStates . StateFocusedDropDown , useTransitions ); VisualStateManager . GoToState ( this , VisualStates . StateFocused , useTransitions ); // Edit States Group if ( IsEditable ) VisualStateManager . GoToState ( this , VisualStates . StateEditable , useTransitions ); VisualStateManager . GoToState ( this , VisualStates . StateUneditable , useTransitions ); base . ChangeVisualState ( useTransitions ); /// < summary > /// If control has a scrollviewer in its style and has a custom keyboard scrolling behavior when HandlesScrolling should return true. /// Then ScrollViewer will not handle keyboard input and leave it up to the control. /// </ summary > protected internal override bool HandlesScrolling get { return true ; } /// < summary > /// Prepare the element to display the item. This may involve /// applying styles, setting bindings, etc. /// </ summary > protected override void PrepareContainerForItemOverride ( DependencyObject element , object item ) base . PrepareContainerForItemOverride ( element , item ); if ( item is Separator ) Separator . PrepareContainer ( element as Control ); /// < summary > /// Adjust ItemInfos when the Items property changes. /// </ summary > internal override void AdjustItemInfoOverride ( NotifyCollectionChangedEventArgs e ) AdjustItemInfo ( e , _highlightedInfo ); base . AdjustItemInfoOverride ( e ); /// < summary > /// Called when this element or any below gets focus. /// </ summary > private static void OnGotFocus ( object sender , RoutedEventArgs e ) // When ComboBox gets logical focus, select the text inside us. ComboBox comboBox = ( ComboBox ) sender ; // If we're an editable combobox, forward focus to the TextBox element if (! e . Handled ) if ( comboBox . IsEditable && comboBox . EditableTextBoxSite != null ) if ( e . OriginalSource == comboBox ) comboBox . EditableTextBoxSite . Focus (); e . Handled = true ; else if ( e . OriginalSource == comboBox . EditableTextBoxSite ) comboBox . EditableTextBoxSite . SelectAll (); /// < summary > /// Called when an item is being focused /// </ summary > internal override bool FocusItem ( ItemInfo info , ItemNavigateArgs itemNavigateArgs ) bool returnValue = false ; // The base implementation sets focus, and we don't want to do that // if we're editable. if (! IsEditable ) returnValue = base . FocusItem ( info , itemNavigateArgs ); ComboBoxItem cbi = info . Container as ComboBoxItem ; HighlightedInfo = ( cbi != null ) ? info : null ; // When IsEditable is 'true', we'll always want to commit the selection. e.g. when user press KeyUp/Down. // However, when IsEditable is 'false' and Dropdown is open, we could get here when user navigate to // the item using ITS. In this case, we don't want to commit the selection. if (( IsEditable || (! IsDropDownOpen )) && itemNavigateArgs . DeviceUsed is KeyboardDevice ) int index = info . Index ; if ( index < 0) index = Items . IndexOf ( info . Item ); SetCurrentValueInternal( SelectedIndexProperty , index ); returnValue = true ; return returnValue ; /// < summary > /// An event reporting that the IsKeyboardFocusWithin property changed. /// </ summary > protected override void OnIsKeyboardFocusWithinChanged ( DependencyPropertyChangedEventArgs e ) base . OnIsKeyboardFocusWithinChanged ( e ); // This is for the case when focus goes elsewhere and the popup is still open; make sure it is closed. if ( IsDropDownOpen && ! IsKeyboardFocusWithin ) // IsKeyboardFocusWithin still flickers under certain conditions. The case // we care about is focus going from the ComboBox to a ComboBoxItem. // Here we can just check if something has focus and if it's a child // of ours or is a context menu that opened below us. DependencyObject currentFocus = Keyboard . FocusedElement as DependencyObject ; if ( currentFocus == null || (! IsContextMenuOpen && ItemsControlFromItemContainer ( currentFocus ) != this )) Close (); CoerceValue ( IsSelectionBoxHighlightedProperty ); /// < summary > /// An event reporting a mouse wheel rotation. /// </ summary > private static void OnMouseWheel ( object sender , MouseWheelEventArgs e ) ComboBox comboBox = ( ComboBox ) sender ; // If we get a mouse wheel event we should scroll when the // drop down is closed and eat the mouse wheel when it's open. // (If the drop down is open and has a scrollviewer, the scrollviewer // will handle it before we get here). // We should only do this when focus is within the combobox, // otherwise we could severely confuse the user. if ( comboBox . IsKeyboardFocusWithin ) if (! comboBox . IsDropDownOpen ) // Negative delta means "down", which means we should move next in that case. if ( e . Delta < 0) comboBox . SelectNext (); comboBox . SelectPrev (); e . Handled = true ; // If focus isn't within the combobox (say, we're not focusable) // but we get a mouse wheel event, we should do nothing unless // the drop down is open, in which case we should eat it. if ( comboBox . IsDropDownOpen ) e . Handled = true ; private static void OnContextMenuOpen ( object sender , ContextMenuEventArgs e ) (( ComboBox ) sender ). IsContextMenuOpen = true ; private static void OnContextMenuClose ( object sender , ContextMenuEventArgs e ) (( ComboBox ) sender ). IsContextMenuOpen = false ; /// < summary > /// Called when IsMouseCaptured changes on this element. /// </ summary > /// < param name = " e " > </ param > protected override void OnIsMouseCapturedChanged ( DependencyPropertyChangedEventArgs e ) // When we take capture, we should start a timer to call // us back and do auto scrolling behavior. if ( IsMouseCaptured ) Debug . Assert ( _autoScrollTimer == null , "IsMouseCaptured went from true to true" ); if ( _autoScrollTimer == null ) _autoScrollTimer = new DispatcherTimer ( DispatcherPriority . SystemIdle ); _autoScrollTimer . Interval = AutoScrollTimeout ; _autoScrollTimer . Tick += new EventHandler ( OnAutoScrollTimeout ); _autoScrollTimer . Start (); if ( _autoScrollTimer != null ) _autoScrollTimer . Stop (); _autoScrollTimer = null ; base . OnIsMouseCapturedChanged ( e ); /// < summary > /// Determines if the ComboBox effectively has focus or not /// based on IsEditable and EditableTextBoxSite.IsKeyboardFocused. /// </ summary > protected internal override bool HasEffectiveKeyboardFocus if ( IsEditable && EditableTextBoxSite != null ) return EditableTextBoxSite .HasEffectiveKeyboardFocus; return base . HasEffectiveKeyboardFocus ; # endregion # region Internal Methods // Helper function called by ComboBoxItem when it receives a MouseDown internal void NotifyComboBoxItemMouseDown ( ComboBoxItem comboBoxItem ) // Helper function called by ComboBoxItem when it receives a MouseUp internal void NotifyComboBoxItemMouseUp ( ComboBoxItem comboBoxItem ) object item = ItemContainerGenerator . ItemFromContainer ( comboBoxItem ); if ( item != null ) SelectionChange . SelectJustThisItem ( NewItemInfo ( item , comboBoxItem ), true /* assumeInItemsCollection */ ); Close (); // Called when Item is entered via mouse or keyboard focus internal void NotifyComboBoxItemEnter ( ComboBoxItem item ) // When a ComboBoxItem is entered, it should be highlighted (and focused). // Note: We may reach this before a nested combo box can grab capture // if one of its items releases capture. In this case, ignore the // enter event if ( IsDropDownOpen && Mouse . Captured == this && DidMouseMove ()) HighlightedInfo = ItemInfoFromContainer ( item ); if (! IsEditable && ! item . IsKeyboardFocusWithin ) item . Focus (); /// < summary > /// Return true if the item is (or is eligible to be) its own ItemUI /// </ summary > protected override bool IsItemItsOwnContainerOverride ( object item ) return ( item is ComboBoxItem ); /// < summary > Create or identify the element used to display the given item. </ summary > protected override DependencyObject GetContainerForItemOverride () return new ComboBoxItem (); # endregion # region Private Methods private void Initialize () CanSelectMultiple = false ; /// < summary > /// An event reporting a key was pressed /// </ summary > protected override void OnPreviewKeyDown ( KeyEventArgs e ) // Only process preview key events if they going to our editable text box if ( IsEditable && e . OriginalSource == EditableTextBoxSite ) KeyDownHandler ( e ); /// < summary > /// An event reporting a key was pressed /// </ summary > protected override void OnKeyDown ( KeyEventArgs e ) KeyDownHandler ( e ); private void KeyDownHandler ( KeyEventArgs e ) bool handled = false ; Key key = e . Key ; // We want to handle Alt key. Get the real key if it is Key.System. if ( key == Key . System ) key = e . SystemKey ; // In Right to Left mode we switch Right and Left keys bool isRTL = ( FlowDirection == FlowDirection . RightToLeft ); switch ( key ) case Key . Up : handled = true ; if (( e . KeyboardDevice . Modifiers & ModifierKeys . Alt ) == ModifierKeys . Alt ) KeyboardToggleDropDown ( true /* commitSelection */ ); // When the drop down isn't open then focus is on the ComboBox // and we can't use KeyboardNavigation. if ( IsItemsHostVisible ) NavigateByLine ( HighlightedInfo , FocusNavigationDirection . Up , new ItemNavigateArgs ( e . Device , Keyboard . Modifiers )); SelectPrev (); break ; case Key . Down : handled = true ; if (( e . KeyboardDevice . Modifiers & ModifierKeys . Alt ) == ModifierKeys . Alt ) KeyboardToggleDropDown ( true /* commitSelection */ ); if ( IsItemsHostVisible ) NavigateByLine ( HighlightedInfo , FocusNavigationDirection . Down , new ItemNavigateArgs ( e . Device , Keyboard . Modifiers )); SelectNext (); break ; case Key . F4 : if (( e . KeyboardDevice . Modifiers & ModifierKeys . Alt ) == 0) KeyboardToggleDropDown ( true /* commitSelection */ ); handled = true ; break ; case Key . Escape : if ( IsDropDownOpen ) KeyboardCloseDropDown ( false /* commitSelection */ ); handled = true ; break ; case Key . Enter : if ( IsDropDownOpen ) KeyboardCloseDropDown ( true /* commitSelection */ ); handled = true ; break ; case Key . Home : if (( e . KeyboardDevice . Modifiers & ModifierKeys . Alt ) != ModifierKeys . Alt && ! IsEditable ) if ( IsItemsHostVisible ) NavigateToStart ( new ItemNavigateArgs ( e . Device , Keyboard . Modifiers )); SelectFirst (); handled = true ; break ; case Key . End : if (( e . KeyboardDevice . Modifiers & ModifierKeys . Alt ) != ModifierKeys . Alt && ! IsEditable ) if ( IsItemsHostVisible ) NavigateToEnd ( new ItemNavigateArgs ( e . Device , Keyboard . Modifiers )); SelectLast (); handled = true ; break ; case Key . Right : if (( e . KeyboardDevice . Modifiers & ModifierKeys . Alt ) != ModifierKeys . Alt && ! IsEditable ) if ( IsItemsHostVisible ) NavigateByLine ( HighlightedInfo , FocusNavigationDirection . Right , new ItemNavigateArgs ( e . Device , Keyboard . Modifiers )); if (! isRTL ) SelectNext (); // If it's RTL then Right should go backwards SelectPrev (); handled = true ; break ; case Key . Left : if (( e . KeyboardDevice . Modifiers & ModifierKeys . Alt ) != ModifierKeys . Alt && ! IsEditable ) if ( IsItemsHostVisible ) NavigateByLine ( HighlightedInfo , FocusNavigationDirection . Left , new ItemNavigateArgs ( e . Device , Keyboard . Modifiers )); if (! isRTL ) SelectPrev (); // If it's RTL then Left should go the other direction SelectNext (); handled = true ; break ; case Key . PageUp : if ( IsItemsHostVisible ) NavigateByPage ( HighlightedInfo , FocusNavigationDirection . Up , new ItemNavigateArgs ( e . Device , Keyboard . Modifiers )); handled = true ; break ; case Key . PageDown : if ( IsItemsHostVisible ) NavigateByPage ( HighlightedInfo , FocusNavigationDirection . Down , new ItemNavigateArgs ( e . Device , Keyboard . Modifiers )); handled = true ; break ; case Key . Oem5 : if ( Keyboard . Modifiers == ModifierKeys . Control ) // If Control is pressed (without Alt, Shift or Windows being pressed) // Scroll into view the selected item -- we want to highlight the item // that we scroll in, so we should navigate to it. NavigateToItem ( InternalSelectedInfo , ItemNavigateArgs . Empty ); handled = true ; break ; default : handled = false ; break ; if ( handled ) e . Handled = true ; private void SelectPrev () if (! Items . IsEmpty ) int selectedIndex = InternalSelectedIndex ; // Search backwards from SelectedIndex - 1 but don't start before the beginning. // If SelectedIndex is less than 0, there is nothing to select before this item. if ( selectedIndex > 0) SelectItemHelper ( selectedIndex - 1, -1, -1); private void SelectNext () int count = Items . Count ; if ( count > 0) int selectedIndex = InternalSelectedIndex ; // Search forwards from SelectedIndex + 1 but don't start past the end. // If SelectedIndex is before the last item then there is potentially // something afterwards that we could select. if ( selectedIndex < count - 1) SelectItemHelper ( selectedIndex + 1, +1, count ); private void SelectFirst () SelectItemHelper (0, +1, Items . Count ); private void SelectLast () SelectItemHelper ( Items . Count - 1, -1, -1); // Walk in the specified direction until we get to a selectable // item or to the stopIndex. // NOTE: stopIndex is not inclusive (it should be one past the end of the range) private void SelectItemHelper ( int startIndex , int increment , int stopIndex ) Debug . Assert (( increment > 0 && startIndex <= stopIndex ) || ( increment < 0 && startIndex >= stopIndex ), "Infinite loop detected" ); for ( int i = startIndex ; i != stopIndex ; i += increment ) // If the item is selectable and the wrapper is selectable, select it. // Need to check both because the user could set any combination of // IsSelectable and IsEnabled on the item and wrapper. object item = Items [ i ]; DependencyObject container = ItemContainerGenerator . ContainerFromIndex ( i ); if ( IsSelectableHelper ( item ) && IsSelectableHelper ( container )) SelectionChange . SelectJustThisItem ( NewItemInfo ( item , container , i ), true /* assumeInItemsCollection */ ); break ; private bool IsSelectableHelper ( object o ) DependencyObject d = o as DependencyObject ; // If o is not a DependencyObject, it is just a plain // object and must be selectable and enabled. if ( d == null ) return true ; // It's selectable if IsSelectable is true and IsEnabled is true. return ( bool ) d . GetValue ( FrameworkElement . IsEnabledProperty ); private static string ExtractString ( DependencyObject d ) TextBlock text ; Visual visual ; TextElement textElement ; string strValue = String . Empty ; if (( text = d as TextBlock ) != null ) strValue = text . Text ; else if (( visual = d as Visual ) != null ) int count = VisualTreeHelper . GetChildrenCount ( visual ); for ( int i = 0; i < count ; i ++) strValue += ExtractString (( DependencyObject )( VisualTreeHelper . GetChild ( visual , i ))); else if (( textElement = d as TextElement ) != null ) strValue += TextRangeBase . GetTextInternal ( textElement . ContentStart , textElement . ContentEnd ); return strValue ; /// < summary > /// Called when the Template's tree has been generated /// </ summary > public override void OnApplyTemplate () base . OnApplyTemplate (); if ( _dropDownPopup != null ) _dropDownPopup . Closed -= OnPopupClosed ; EditableTextBoxSite = GetTemplateChild ( EditableTextBoxTemplateName ) as TextBox ; _dropDownPopup = GetTemplateChild ( PopupTemplateName ) as Popup ; // EditableTextBoxSite should have been set by now if it's in the visual tree if ( EditableTextBoxSite != null ) EditableTextBoxSite . TextChanged += new TextChangedEventHandler ( OnEditableTextBoxTextChanged ); EditableTextBoxSite . SelectionChanged += new RoutedEventHandler ( OnEditableTextBoxSelectionChanged ); EditableTextBoxSite . PreviewTextInput += new TextCompositionEventHandler ( OnEditableTextBoxPreviewTextInput ); if ( _dropDownPopup != null ) _dropDownPopup . Closed += OnPopupClosed ; Update (); internal override void OnTemplateChangedInternal ( FrameworkTemplate oldTemplate , FrameworkTemplate newTemplate ) base . OnTemplateChangedInternal ( oldTemplate , newTemplate ); // This is called when a template is applied but before the new template has been inflated. // If we had a style before, detach from event handlers if ( EditableTextBoxSite != null ) EditableTextBoxSite . TextChanged -= new TextChangedEventHandler ( OnEditableTextBoxTextChanged ); EditableTextBoxSite . SelectionChanged -= new RoutedEventHandler ( OnEditableTextBoxSelectionChanged ); EditableTextBoxSite . PreviewTextInput -= new TextCompositionEventHandler ( OnEditableTextBoxPreviewTextInput ); # region Capture private static void OnLostMouseCapture ( object sender , MouseEventArgs e ) ComboBox comboBox = ( ComboBox ) sender ; // ISSUE (jevansa) -- task 22022: // We need a general mechanism to do this, or at the very least we should // share it amongst the controls which need it (Popup, MenuBase, ComboBox). if ( Mouse . Captured != comboBox ) if ( e . OriginalSource == comboBox ) // If capture is null or it's not below the combobox, close. // More workaround for task 22022 -- check if it's a descendant (following Logical links too) if ( Mouse . Captured == null || ! MenuBase . IsDescendant ( comboBox , Mouse . Captured as DependencyObject )) comboBox . Close (); if ( MenuBase . IsDescendant ( comboBox , e . OriginalSource as DependencyObject )) // Take capture if one of our children gave up capture (by closing their drop down) if ( comboBox . IsDropDownOpen && Mouse . Captured == null && MS.Win32.SafeNativeMethods.GetCapture() == IntPtr . Zero ) Mouse . Capture ( comboBox , CaptureMode . SubTree ); e . Handled = true ; comboBox . Close (); private static void OnMouseButtonDown ( object sender , MouseButtonEventArgs e ) ComboBox comboBox = ( ComboBox ) sender ; // If we (or one of our children) are clicked, claim the focus (don't steal focus if our context menu is clicked) if (! comboBox . IsContextMenuOpen && ! comboBox . IsKeyboardFocusWithin ) comboBox . Focus (); e . Handled = true ; // Always handle so that parents won't take focus away // Note: This half should be moved into OnMouseDownOutsideCapturedElement // When we have capture, all clicks off the popup will have the combobox as // the OriginalSource. So when the original source is the combobox, that // means the click was off the popup and we should dismiss. if ( Mouse . Captured == comboBox && e . OriginalSource == comboBox ) comboBox . Close (); Debug . Assert (! comboBox . CheckAccess () || Mouse . Captured != comboBox , "On the dispatcher thread, ComboBox should not have capture after closing the dropdown" ); private static void OnPreviewMouseButtonDown ( object sender , MouseButtonEventArgs e ) ComboBox comboBox = ( ComboBox ) sender ; if ( comboBox . IsEditable ) Visual originalSource = e . OriginalSource as Visual ; Visual textBox = comboBox . EditableTextBoxSite ; if ( originalSource != null && textBox != null && textBox . IsAncestorOf ( originalSource )) if ( comboBox . IsDropDownOpen && ! comboBox . StaysOpenOnEdit ) // When combobox is not editable, clicks anywhere outside // the combobox will close it. When the combobox is editable // then clicking the text box should close the combobox as well. comboBox . Close (); else if (! comboBox . IsContextMenuOpen && ! comboBox . IsKeyboardFocusWithin ) // If textBox is clicked, claim focus comboBox . Focus (); e . Handled = true ; // Handle so that textbox won't try to update cursor position /// < summary > /// An event reporting the left mouse button was released. /// </ summary > /// < param name = " e " > </ param > protected override void OnMouseLeftButtonUp ( MouseButtonEventArgs e ) // Ignore the first mouse button up if we haven't gone over the popup yet // And ignore all mouse ups over the items host. if ( HasMouseEnteredItemsHost && ! IsMouseOverItemsHost ) if ( IsDropDownOpen ) Close (); e . Handled = true ; Debug . Assert (! CheckAccess () || Mouse . Captured != this , "On the dispatcher thread, ComboBox should not have capture after closing the dropdown" ); base . OnMouseLeftButtonUp ( e ); private static void OnMouseMove ( object sender , MouseEventArgs e ) ComboBox comboBox = ( ComboBox ) sender ; // The mouse moved, see if we're over the items host yet if ( comboBox . IsDropDownOpen ) bool isMouseOverItemsHost = comboBox . ItemsHost != null ? comboBox . ItemsHost . IsMouseOver : false ; // When mouse enters items host, start tracking mouse movements if ( isMouseOverItemsHost && ! comboBox . HasMouseEnteredItemsHost ) comboBox . SetInitialMousePosition (); comboBox . IsMouseOverItemsHost = isMouseOverItemsHost ; comboBox . HasMouseEnteredItemsHost |= isMouseOverItemsHost ; // If we get a mouse move and we have capture, then the mouse was // outside the ComboBox. We should autoscroll. if ( Mouse . LeftButton == MouseButtonState . Pressed && comboBox . HasMouseEnteredItemsHost ) if ( Mouse . Captured == comboBox ) if ( Mouse . LeftButton == MouseButtonState . Pressed ) comboBox . DoAutoScroll ( comboBox . HighlightedInfo ); // We missed the mouse up, release capture comboBox . ReleaseMouseCapture (); comboBox . ResetLastMousePosition (); e . Handled = true ; /// < summary > /// Called to toggle the DropDown using the keyboard. /// </ summary > private void KeyboardToggleDropDown ( bool commitSelection ) KeyboardToggleDropDown (! IsDropDownOpen , commitSelection ); /// < summary > /// Called to close the DropDown using the keyboard. /// </ summary > private void KeyboardCloseDropDown ( bool commitSelection ) KeyboardToggleDropDown ( false /* openDropDown */ , commitSelection ); private void KeyboardToggleDropDown ( bool openDropDown , bool commitSelection ) // Close the dropdown and commit the selection if requested. // Make sure to set the selection after the dropdown has closed // so we don't trigger any unnecessary navigation as a result // of changing the selection. ItemInfo infoToSelect = null ; if ( commitSelection ) infoToSelect = HighlightedInfo ; SetCurrentValueInternal( IsDropDownOpenProperty , BooleanBoxes.Box( openDropDown )); if ( openDropDown == false && commitSelection && ( infoToSelect != null )) SelectionChange . SelectJustThisItem ( infoToSelect , true /* assumeInItemsCollection */ ); private void CommitSelection () ItemInfo infoToSelect = HighlightedInfo ; if ( infoToSelect != null ) SelectionChange . SelectJustThisItem ( infoToSelect , true /* assumeInItemsCollection */ ); private void OnAutoScrollTimeout ( object sender , EventArgs e ) if ( Mouse . LeftButton == MouseButtonState . Pressed && HasMouseEnteredItemsHost ) DoAutoScroll ( HighlightedInfo ); private void Close () if ( IsDropDownOpen ) SetCurrentValueInternal( IsDropDownOpenProperty , false ); # endregion # endregion # region Accessibility /// < summary > /// Creates AutomationPeer ( < see cref = " UIElement . OnCreateAutomationPeer " /> ) /// </ summary > protected override AutomationPeer OnCreateAutomationPeer () return new ComboBoxAutomationPeer ( this ); # endregion # region Private Properties internal TextBox EditableTextBoxSite return _editableTextBoxSite ; _editableTextBoxSite = value ; private bool HasCapture return Mouse . Captured == this ; /// < summary > /// Returns true if the ItemsHost is visually connected to the RootVisual of its PresentationSource. /// </ summary > /// < value > </ value > /// < SecurityNote > /// Critical: This code accesses HwndSource from the call PresentationSource.CriticalFromVisual /// TreatAsSafe: It does not expose the critical data /// </ SecurityNote > private bool IsItemsHostVisible [ SecurityCritical , SecurityTreatAsSafe ] Panel itemsHost = ItemsHost ; if ( itemsHost != null ) HwndSource source = PresentationSource .CriticalFromVisual( itemsHost ) as HwndSource ; if ( source != null && ! source . IsDisposed && source . RootVisual != null ) return source . RootVisual . IsAncestorOf ( itemsHost ); return false ; private ItemInfo HighlightedInfo get { return _highlightedInfo ; } ComboBoxItem cbi = ( _highlightedInfo != null ) ? _highlightedInfo . Container as ComboBoxItem : null ; if ( cbi != null ) cbi . SetIsHighlighted ( false ); _highlightedInfo = value ; cbi = ( _highlightedInfo != null ) ? _highlightedInfo . Container as ComboBoxItem : null ; if ( cbi != null ) cbi . SetIsHighlighted ( true ); CoerceValue ( IsSelectionBoxHighlightedProperty ); private ComboBoxItem HighlightedElement get { return ( _highlightedInfo == null ) ? null : _highlightedInfo . Container as ComboBoxItem ; } private bool IsMouseOverItemsHost get { return _cacheValid [( int ) CacheBits . IsMouseOverItemsHost ]; } set { _cacheValid [( int ) CacheBits . IsMouseOverItemsHost ] = value ; } private bool HasMouseEnteredItemsHost get { return _cacheValid [( int ) CacheBits . HasMouseEnteredItemsHost ]; } set { _cacheValid [( int ) CacheBits . HasMouseEnteredItemsHost ] = value ; } private bool IsContextMenuOpen get { return _cacheValid [( int ) CacheBits . IsContextMenuOpen ]; } set { _cacheValid [( int ) CacheBits . IsContextMenuOpen ] = value ; } // Used to indicate that the Text Properties are changing // Don't reenter callbacks private bool UpdatingText get { return _cacheValid [( int ) CacheBits . UpdatingText ]; } set { _cacheValid [( int ) CacheBits . UpdatingText ] = value ; } // Selected item is being updated; Don't reenter callbacks private bool UpdatingSelectedItem get { return _cacheValid [( int ) CacheBits . UpdatingSelectedItem ]; } set { _cacheValid [( int ) CacheBits . UpdatingSelectedItem ] = value ; } // A text composition is active (in the EditableTextBoxSite); postpone Text changes private bool IsWaitingForTextComposition get { return _cacheValid [( int ) CacheBits . IsWaitingForTextComposition ]; } set { _cacheValid [( int ) CacheBits . IsWaitingForTextComposition ] = value ; } # endregion # region Private Members private const string EditableTextBoxTemplateName = "PART_EditableTextBox" ; private const string PopupTemplateName = "PART_Popup" ; private TextBox _editableTextBoxSite ; private Popup _dropDownPopup ; private int _textBoxSelectionStart ; // the location of selection before call to TextUpdated. private BitVector32 _cacheValid = new BitVector32 (0); // Condense boolean bits private ItemInfo _highlightedInfo ; // info about the ComboBoxItem which is "highlighted" private DispatcherTimer _autoScrollTimer ; private UIElement _clonedElement ; private DispatcherOperation _updateTextBoxOperation ; private enum CacheBits IsMouseOverItemsHost = 0x01, HasMouseEnteredItemsHost = 0x02, IsContextMenuOpen = 0x04, UpdatingText = 0x08, UpdatingSelectedItem = 0x10, IsWaitingForTextComposition = 0x20, # endregion Private Members # region DTypeThemeStyleKey // Returns the DependencyObjectType for the registered ThemeStyleKey's default // value. Controls will override this method to return approriate types. internal override DependencyObjectType DTypeThemeStyleKey get { return _dType ; } private static DependencyObjectType _dType ;