Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Controls / TextSearch.cs / 1305600 / TextSearch.cs
//---------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using System.Diagnostics; using System.Windows; using System.Windows.Threading; using System.Windows.Data; using System.ComponentModel; using System.Windows.Input; using System.Collections; using MS.Win32; using System.Globalization; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Markup; // for XmlLanguage using System.Windows.Media; using System.Text; using System.Collections.Generic; using MS.Internal; using MS.Internal.Data; namespace System.Windows.Controls { // ////// Text Search is a feature that allows the user to quickly access items in a set by typing prefixes of the strings. /// public sealed class TextSearch : DependencyObject { ////// Make a new TextSearch instance attached to the given object. /// Create the instance in the same context as the given DO. /// /// private TextSearch(ItemsControl itemsControl) { if (itemsControl == null) { throw new ArgumentNullException("itemsControl"); } _attachedTo = itemsControl; ResetState(); } ////// Get the instance of TextSearch attached to the given ItemsControl or make one and attach it if it's not. /// /// ///internal static TextSearch EnsureInstance(ItemsControl itemsControl) { TextSearch instance = (TextSearch)itemsControl.GetValue(TextSearchInstanceProperty); if (instance == null) { instance = new TextSearch(itemsControl); itemsControl.SetValue(TextSearchInstancePropertyKey, instance); } return instance; } #region Text and TextPath Properties /// /// Attached property to indicate which property on the item in the items collection to use for the "primary" text, /// or the text against which to search. /// public static readonly DependencyProperty TextPathProperty = DependencyProperty.RegisterAttached("TextPath", typeof(string), typeof(TextSearch), new FrameworkPropertyMetadata(String.Empty /* default value */)); ////// Writes the attached property to the given element. /// /// /// public static void SetTextPath(DependencyObject element, string path) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(TextPathProperty, path); } ////// Reads the attached property from the given element. /// /// ///[AttachedPropertyBrowsableForType(typeof(DependencyObject))] public static string GetTextPath(DependencyObject element) { if (element == null) { throw new ArgumentNullException("element"); } return (string)element.GetValue(TextPathProperty); } /// /// Attached property to indicate the value to use for the "primary" text of an element. /// public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached("Text", typeof(string), typeof(TextSearch), new FrameworkPropertyMetadata((string)String.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); ////// Writes the attached property to the given element. /// /// /// public static void SetText(DependencyObject element, string text) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(TextProperty, text); } ////// Reads the attached property from the given element. /// /// ///[AttachedPropertyBrowsableForType(typeof(DependencyObject))] public static string GetText(DependencyObject element) { if (element == null) { throw new ArgumentNullException("element"); } return (string)element.GetValue(TextProperty); } #endregion #region Properties /// /// Prefix that is currently being used in the algorithm. /// private static readonly DependencyProperty CurrentPrefixProperty = DependencyProperty.RegisterAttached("CurrentPrefix", typeof(string), typeof(TextSearch), new FrameworkPropertyMetadata((string)null)); ////// If TextSearch is currently active. /// private static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(TextSearch), new FrameworkPropertyMetadata(false)); #endregion #region Private Properties ////// The key needed set a read-only property. /// private static readonly DependencyPropertyKey TextSearchInstancePropertyKey = DependencyProperty.RegisterAttachedReadOnly("TextSearchInstance", typeof(TextSearch), typeof(TextSearch), new FrameworkPropertyMetadata((object)null /* default value */)); ////// Instance of TextSearch -- attached property so that the instance can be stored on the element /// which wants the service. /// private static readonly DependencyProperty TextSearchInstanceProperty = TextSearchInstancePropertyKey.DependencyProperty; // used to retrieve the value of an item, according to the TextPath private static readonly BindingExpressionUncommonField TextValueBindingExpression = new BindingExpressionUncommonField(); #endregion #region Private Methods ////// Called by consumers of TextSearch when a TextInput event is received /// to kick off the algorithm. /// /// ///internal bool DoSearch(string nextChar) { bool repeatedChar = false; int startItemIndex = 0; ItemCollection itemCollection = _attachedTo.Items as ItemCollection; // If TextSearch is not active, then we should start // the search from the beginning. If it is active, we should // start the search from the currently-matched item. if (IsActive) { // ISSUE: This falls victim to duplicate elements being in the view. // To mitigate this, we could remember ItemUI ourselves. startItemIndex = MatchedItemIndex; } // If they pressed the same character as last time, we will do the fallback search. // Fallback search is if they type "bob" and then press "b" // we'll look for "bobb" and when we don't find it we should // find the next item starting with "bob". if (_charsEntered.Count > 0 && (String.Compare(_charsEntered[_charsEntered.Count - 1], nextChar, true, GetCulture(_attachedTo))==0)) { repeatedChar = true; } // Get the primary TextPath from the ItemsControl to which we are attached. string primaryTextPath = GetPrimaryTextPath(_attachedTo); bool wasNewCharUsed = false; int matchedItemIndex = FindMatchingPrefix(_attachedTo, primaryTextPath, Prefix, nextChar, startItemIndex, repeatedChar, ref wasNewCharUsed); // If there was an item that matched, move to that item in the collection if (matchedItemIndex != -1) { // Don't have to move currency if it didn't actually move. // startItemIndex is the index of the current item only if IsActive is true, // So, we have to move currency when IsActive is false. if (!IsActive || matchedItemIndex != startItemIndex) { object matchedItem = itemCollection[matchedItemIndex]; // Let the control decide what to do with matched-item _attachedTo.NavigateToItem(matchedItem, matchedItemIndex, new ItemsControl.ItemNavigateArgs(Keyboard.PrimaryDevice, ModifierKeys.None)); // Store current match MatchedItemIndex = matchedItemIndex; } // Update the prefix if it changed if (wasNewCharUsed) { AddCharToPrefix(nextChar); } // User has started typing (successfully), so we're active now. if (!IsActive) { IsActive = true; } } // Reset the timeout and remember this character, but only if we're // active -- this is because if we got called but the match failed // we don't need to set up a timeout -- no state needs to be reset. if (IsActive) { ResetTimeout(); } return (matchedItemIndex != -1); } /// /// Called when the user presses backspace. /// ///internal bool DeleteLastCharacter() { if (IsActive) { // Remove the last character from the prefix string. // Get the last character entered and then remove a string of // that length off the prefix string. if (_charsEntered.Count > 0) { string lastChar = _charsEntered[_charsEntered.Count - 1]; string prefix = Prefix; _charsEntered.RemoveAt(_charsEntered.Count - 1); Prefix = prefix.Substring(0, prefix.Length - lastChar.Length); ResetTimeout(); return true; } } return false; } /// /// Searches through the given itemCollection for the first item matching the given prefix. /// ////// ------------------------------------------------------------------------- /// Incremental Type Search algorithm /// ------------------------------------------------------------------------- /// /// Given a prefix and new character, we loop through all items in the collection /// and look for an item that starts with the new prefix. If we find such an item, /// select it. If the new character is repeated, we look for the next item after /// the current one that begins with the old prefix**. We can optimize by /// performing both of these searches in parallel. /// /// **NOTE: Win32 will only do this if the old prefix is of length 1 - in other /// words, first-character-only matching. The algorithm described here /// is an extension of ITS as implemented in Win32. This variant was /// described to me by JeffBog as what was done in AFC - but I have yet /// to find a listbox which behaves this way. /// /// -------------------------------------------------------------------------- /// ///Item that matches the given prefix private static int FindMatchingPrefix(ItemsControl itemsControl, string primaryTextPath, string prefix, string newChar, int startItemIndex, bool lookForFallbackMatchToo, ref bool wasNewCharUsed) { ItemCollection itemCollection = itemsControl.Items; // Using indices b/c this is a better way to uniquely // identify an element in the collection. int matchedItemIndex = -1; int fallbackMatchIndex = -1; int count = itemCollection.Count; // Return immediately with no match if there were no items in the view. if (count == 0) { return -1; } string newPrefix = prefix + newChar; // With an empty prefix, we'd match anything if (String.IsNullOrEmpty(newPrefix)) { return -1; } // Hook up the binding we will apply to each object. Get the // PrimaryTextPath off of the attached instance and then make // a binding with that path. BindingExpression primaryTextBinding = null; object item0 = itemsControl.Items[0]; bool useXml = AssemblyHelper.IsXmlNode(item0); if (useXml || !String.IsNullOrEmpty(primaryTextPath)) { primaryTextBinding = CreateBindingExpression(itemsControl, item0, primaryTextPath); TextValueBindingExpression.SetValue(itemsControl, primaryTextBinding); } bool firstItem = true; wasNewCharUsed = false; CultureInfo cultureInfo = GetCulture(itemsControl); // ISSUE: what about changing the collection while this is running? for (int currentIndex = startItemIndex; currentIndex < count; ) { object item = itemCollection[currentIndex]; if (item != null) { string itemString = GetPrimaryText(item, primaryTextBinding, itemsControl); bool isTextSearchCaseSensitive = itemsControl.IsTextSearchCaseSensitive; // See if the current item matches the newPrefix, if so we can // stop searching and accept this item as the match. if (itemString != null && itemString.StartsWith(newPrefix, !isTextSearchCaseSensitive, cultureInfo)) { // Accept the new prefix as the current prefix. wasNewCharUsed = true; matchedItemIndex = currentIndex; break; } // Find the next string that matches the last prefix. This // string will be used in the case that the new prefix isn't // matched. This enables pressing the last character multiple // times and cylcing through the set of items that match that // prefix. // // Unlike the above search, this search must start *after* // the currently selected item. This search also shouldn't // happen if there was no previous prefix to match against if (lookForFallbackMatchToo) { if (!firstItem && prefix != String.Empty) { if (itemString != null) { if (fallbackMatchIndex == -1 && itemString.StartsWith(prefix, !isTextSearchCaseSensitive, cultureInfo)) { fallbackMatchIndex = currentIndex; } } } else { firstItem = false; } } } // Move next and wrap-around if we pass the end of the container. currentIndex++; if (currentIndex >= count) { currentIndex = 0; } // Stop where we started but only after the first pass // through the loop -- we should process the startItem. if (currentIndex == startItemIndex) { break; } } if (primaryTextBinding != null) { // Clean up the binding for the primary text path. TextValueBindingExpression.ClearValue(itemsControl); } // In the case that the new prefix didn't match anything and // there was a fallback match that matched the old prefix, move // to that one. if (matchedItemIndex == -1 && fallbackMatchIndex != -1) { matchedItemIndex = fallbackMatchIndex; } return matchedItemIndex; } ////// Helper function called by Editable ComboBox to search through items. /// internal static int FindMatchingPrefix(ItemsControl itemsControl, string prefix) { bool wasNewCharUsed = false; return FindMatchingPrefix(itemsControl, GetPrimaryTextPath(itemsControl), prefix, String.Empty, 0, false, ref wasNewCharUsed); } private void ResetTimeout() { // Called when we get some input. Start or reset the timer. // Queue an inactive priority work item and set its deadline. if (_timeoutTimer == null) { _timeoutTimer = new DispatcherTimer(DispatcherPriority.Normal); _timeoutTimer.Tick += new EventHandler(OnTimeout); } else { _timeoutTimer.Stop(); } // Schedule this operation to happen a certain number of milliseconds from now. _timeoutTimer.Interval = TimeOut; _timeoutTimer.Start(); } private void AddCharToPrefix(string newChar) { Prefix += newChar; _charsEntered.Add(newChar); } private static string GetPrimaryTextPath(ItemsControl itemsControl) { string primaryTextPath = (string)itemsControl.GetValue(TextPathProperty); if (String.IsNullOrEmpty(primaryTextPath)) { primaryTextPath = itemsControl.DisplayMemberPath; } return primaryTextPath; } private static string GetPrimaryText(object item, BindingExpression primaryTextBinding, DependencyObject primaryTextBindingHome) { // Order of precedence for getting Primary Text is as follows: // // 1) PrimaryText // 2) PrimaryTextPath (TextSearch.TextPath or ItemsControl.DisplayMemberPath) // 3) GetPlainText() // 4) ToString() DependencyObject itemDO = item as DependencyObject; if (itemDO != null) { string primaryText = (string)itemDO.GetValue(TextProperty); if (!String.IsNullOrEmpty(primaryText)) { return primaryText; } } // Here hopefully they've supplied a path into their object which we can use. if (primaryTextBinding != null && primaryTextBindingHome != null) { // Take the binding that we hooked up at the beginning of the search // and apply it to the current item. Then, read the value of the // ItemPrimaryText property (where the binding actually lives). // Try to convert the resulting object to a string. primaryTextBinding.Activate(item); object primaryText = primaryTextBinding.Value; return ConvertToPlainText(primaryText); } return ConvertToPlainText(item); } private static string ConvertToPlainText(object o) { FrameworkElement fe = o as FrameworkElement; // Try to return FrameworkElement.GetPlainText() if (fe != null) { string text = fe.GetPlainText(); if (text != null) { return text; } } // Try to convert the item to a string return (o != null) ? o.ToString() : String.Empty; } ////// Internal helper method that uses the same primary text lookup steps but doesn't require /// the user passing in all of the bindings that we need. /// /// /// ///internal static string GetPrimaryTextFromItem(ItemsControl itemsControl, object item) { if (item == null) return String.Empty; BindingExpression primaryTextBinding = CreateBindingExpression(itemsControl, item, GetPrimaryTextPath(itemsControl)); TextValueBindingExpression.SetValue(itemsControl, primaryTextBinding); string primaryText = GetPrimaryText(item, primaryTextBinding, itemsControl); TextValueBindingExpression.ClearValue(itemsControl); return primaryText; } private static BindingExpression CreateBindingExpression(ItemsControl itemsControl, object item, string primaryTextPath) { Binding binding = new Binding(); // Use xpath for xmlnodes (See Selector.PrepareItemValueBinding) if (AssemblyHelper.IsXmlNode(item)) { binding.XPath = primaryTextPath; binding.Path = new PropertyPath("/InnerText"); } else { binding.Path = new PropertyPath(primaryTextPath); } binding.Mode = BindingMode.OneWay; binding.Source = null; return (BindingExpression)BindingExpression.CreateUntargetedBindingExpression(itemsControl, binding); } private void OnTimeout(object sender, EventArgs e) { ResetState(); } private void ResetState() { // Reset the prefix string back to empty. IsActive = false; Prefix = String.Empty; MatchedItemIndex = -1; if (_charsEntered == null) { _charsEntered = new List (10); } else { _charsEntered.Clear(); } if(_timeoutTimer != null) { _timeoutTimer.Stop(); } _timeoutTimer = null; } /// /// Time until the search engine resets. /// private TimeSpan TimeOut { get { // NOTE: NtUser does the following (file: windows/ntuser/kernel/sysmet.c) // gpsi->dtLBSearch = dtTime * 4; // dtLBSearch = 4 * gdtDblClk // gpsi->dtScroll = gpsi->dtLBSearch / 5; // dtScroll = 4/5 * gdtDblClk // // 4 * DoubleClickSpeed seems too slow for the search // So for now we'll do 2 * DoubleClickSpeed return TimeSpan.FromMilliseconds(SafeNativeMethods.GetDoubleClickTime() * 2); } } #endregion #region Testing API // Being that this is a time-sensitive operation, it's difficult // to get the timing right in a DRT. I'll leave input testing up to BVTs here // but this internal API is for the DRT to do basic coverage. private static TextSearch GetInstance(DependencyObject d) { return EnsureInstance(d as ItemsControl); } private void TypeAKey(string c) { DoSearch(c); } private void CauseTimeOut() { if (_timeoutTimer != null) { _timeoutTimer.Stop(); OnTimeout(_timeoutTimer, EventArgs.Empty); } } internal string GetCurrentPrefix() { return Prefix; } #endregion #region Internal Accessibility API internal static string GetPrimaryText(FrameworkElement element) { if (element == null) { throw new ArgumentNullException("element"); } string text = (string)element.GetValue(TextProperty); if (text != null && text != String.Empty) { return text; } return element.GetPlainText(); } #endregion #region Private Fields private string Prefix { get { return _prefix; } set { _prefix = value; #if DEBUG // Also need to invalidate the property CurrentPrefixProperty on the instance to which we are attached. Debug.Assert(_attachedTo != null); _attachedTo.SetValue(CurrentPrefixProperty, _prefix); #endif } } private bool IsActive { get { return _isActive; } set { _isActive = value; #if DEBUG Debug.Assert(_attachedTo != null); _attachedTo.SetValue(IsActiveProperty, _isActive); #endif } } private int MatchedItemIndex { get { return _matchedItemIndex; } set { _matchedItemIndex = value; } } private static CultureInfo GetCulture(DependencyObject element) { object o = element.GetValue(FrameworkElement.LanguageProperty); CultureInfo culture = null; if (o != null) { XmlLanguage language = (XmlLanguage) o; try { culture = language.GetSpecificCulture(); } catch (InvalidOperationException) { } } return culture; } // Element to which this TextSearch instance is attached. private ItemsControl _attachedTo; // String of characters matched so far. private string _prefix; private List_charsEntered; private bool _isActive; private int _matchedItemIndex; private DispatcherTimer _timeoutTimer; #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- DataGridViewRowConverter.cs
- OleDbConnectionInternal.cs
- StringValueConverter.cs
- XmlSchemaAttribute.cs
- ObjectItemCachedAssemblyLoader.cs
- x509store.cs
- DebugController.cs
- DefaultAssemblyResolver.cs
- FixedSOMContainer.cs
- SelectedGridItemChangedEvent.cs
- StoreItemCollection.Loader.cs
- WebPartConnectionsConnectVerb.cs
- TypeConverterMarkupExtension.cs
- PLINQETWProvider.cs
- ICollection.cs
- ConnectionManagementSection.cs
- MsmqIntegrationSecurityElement.cs
- D3DImage.cs
- SignedPkcs7.cs
- DecimalSumAggregationOperator.cs
- FamilyCollection.cs
- AutoGeneratedField.cs
- Error.cs
- PersonalizableAttribute.cs
- SqlDataSourceCommandEventArgs.cs
- LogEntryHeaderv1Deserializer.cs
- AdapterDictionary.cs
- ProviderCommandInfoUtils.cs
- XmlSiteMapProvider.cs
- BindToObject.cs
- ConnectorEditor.cs
- UnicodeEncoding.cs
- MimeXmlReflector.cs
- SubordinateTransaction.cs
- MonitoringDescriptionAttribute.cs
- _AutoWebProxyScriptWrapper.cs
- MailMessageEventArgs.cs
- WebHeaderCollection.cs
- Decoder.cs
- VersionedStreamOwner.cs
- DataGridColumnCollection.cs
- ItemsPanelTemplate.cs
- ComponentRenameEvent.cs
- transactioncontext.cs
- DataGridViewCellLinkedList.cs
- SqlProviderUtilities.cs
- InternalDispatchObject.cs
- Operator.cs
- CatalogPartChrome.cs
- DbConvert.cs
- TextDecorationLocationValidation.cs
- DesignerRegion.cs
- AnimationLayer.cs
- UnsafeNativeMethods.cs
- WSSecurityPolicy11.cs
- NumberFunctions.cs
- DisableDpiAwarenessAttribute.cs
- DoubleAnimationBase.cs
- QilBinary.cs
- HMACSHA384.cs
- Action.cs
- InputMethod.cs
- EntityCollection.cs
- UIElement3DAutomationPeer.cs
- SqlProviderManifest.cs
- TagNameToTypeMapper.cs
- List.cs
- ZipFileInfo.cs
- ConfigPathUtility.cs
- compensatingcollection.cs
- IntegerFacetDescriptionElement.cs
- XmlDictionaryString.cs
- SchemaImporterExtensionElementCollection.cs
- Timeline.cs
- CompressionTransform.cs
- CounterSample.cs
- ResourceContainer.cs
- Track.cs
- OneOf.cs
- StylusShape.cs
- XpsFilter.cs
- PreviewKeyDownEventArgs.cs
- Message.cs
- Rect.cs
- PackageProperties.cs
- ItemCheckEvent.cs
- UnsafeNativeMethods.cs
- CompilerScope.Storage.cs
- ReturnType.cs
- PolyQuadraticBezierSegment.cs
- XmlTypeAttribute.cs
- UIPropertyMetadata.cs
- XsdBuildProvider.cs
- BoundField.cs
- RenderOptions.cs
- PeerTransportListenAddressValidatorAttribute.cs
- BindingListCollectionView.cs
- RenamedEventArgs.cs
- CodeFieldReferenceExpression.cs
- ToolBarDesigner.cs