/// <summary> /// Resolves a locator starting from 'startFrom' and the locator part at /// position 'offset' in the locator. This method is called from the /// DescendentsWalker. It maintains the state of resolution as each /// node is visited and individual locator parts are resolved. /// </summary> /// <param name="dependencyObject">the current node to visit</param> /// <param name="data">data containing the state of the current resolution</param> /// <returns>whether or not the children of this node should be visited</returns> private bool ResolveLocatorPart(DependencyObject dependencyObject, ResolvingLocatorState data, bool visitedViaVisualTree) { if (data.Finished) return false; ContentLocator locator = data.ContentLocatorBase; Debug.Assert(locator != null, "locator can not be null"); Debug.Assert(data.LocatorPartIndex >= 0 && data.LocatorPartIndex < locator.Parts.Count, "LocatorPartIndex out of range"); bool keepResolving = true; DependencyObject node = null; SubTreeProcessor processor = null; ContentLocatorPart locatorPart = locator.Parts[data.LocatorPartIndex]; if (locatorPart == null) { // Can't resolve a null ContentLocatorPart keepResolving = false; } processor = this.GetSubTreeProcessorForLocatorPart(locatorPart); if (processor == null) { // Can't keep resolving if there is no processor for this ContentLocatorBase Part keepResolving = false; } if (locatorPart != null && processor != null) { node = processor.ResolveLocatorPart(locatorPart, dependencyObject, out keepResolving); if (node != null) { // At a minimum we are incompletely resolved data.AttachmentLevel = AttachmentLevel.Incomplete; data.AttachedAnchor = node; keepResolving = true; data.LastNodeMatched = node; data.LocatorPartIndex++; // We might already be finished here - if there are no more locator parts // we are fully resolved and there's no need to keep resolving if (data.LocatorPartIndex == locator.Parts.Count) { data.AttachmentLevel = AttachmentLevel.Full; data.AttachedAnchor = node; keepResolving = false; } // If all we have left is the last locatorPart, lets try to resolve it as a // selection. If there is no selection processor, it will fall through and // be handled by resolving on one of the children else if (data.LocatorPartIndex == locator.Parts.Count - 1) { locatorPart = locator.Parts[data.LocatorPartIndex]; SelectionProcessor selProcessor = GetSelectionProcessorForLocatorPart(locatorPart); if (selProcessor != null) { AttachmentLevel attachmentLevel; Object selection = selProcessor.ResolveLocatorPart(locatorPart, node, out attachmentLevel); if (selection != null) { data.AttachmentLevel = attachmentLevel; data.AttachedAnchor = selection; keepResolving = false; } else { // In this case the processor couldn't resolve the selection // locator part. There's no use in continuing. keepResolving = false; } } } } } return keepResolving; }
/// <summary> /// This gets called after each node's subtree has been called to resolve /// the current locator part. /// If the node the call is made for was the last node that anything was /// matched with, we want to stop looking at the rest of the tree. This is /// because matches should be unique (so no sibling should be able to match) /// and if they aren't the first match wins. /// </summary> /// <param name="dependencyObject"></param> /// <param name="data"></param> /// <returns></returns> private bool TerminateResolve(DependencyObject dependencyObject, ResolvingLocatorState data, bool visitedViaVisualTree) { // If we are finished with the subtree for the last node matched, we've // resolved as much as we can and we should not bother looking at the // rest of the tree. Finished is a property we use to short-circuit // vising the rest of the tree. Note: Returning false only prevents // the children from being visited, we need to not visit siblings either. if (!data.Finished && data.LastNodeMatched == dependencyObject) { data.Finished = true; } return false; }
/// <summary> /// Resolves a single locator starting at the given startNode. /// Sets the selection and attachmentLevel if necessary. /// </summary> /// <param name="selection">object representing the content that has been resolved /// so far; updated if the locator passed in is resolved</param> /// <param name="attachmentLevel">attachmentLevel of content that has been resolved /// so far; updated based on the resolution of the passed in locator</param> /// <param name="attemptedLevel">the level that is represented by this locator - /// start, middle or end</param> /// <param name="locator">the locator to resolve</param> /// <param name="offset">the offset into the locator to start the resolution at</param> /// <param name="startNode">the node to start the resolution at</param> /// <param name="skipStartNode">whether or not the start node should be looked at</param> /// <returns>the data representing the resolution of the single locator; used for /// special cases by calling code to override results from this method</returns> private ResolvingLocatorState ResolveSingleLocator(ref object selection, ref AttachmentLevel attachmentLevel, AttachmentLevel attemptedLevel, ContentLocator locator, int offset, DependencyObject startNode, bool skipStartNode) { ResolvingLocatorState data = new ResolvingLocatorState(); data.LocatorPartIndex = offset; data.ContentLocatorBase = locator; PrePostDescendentsWalker<ResolvingLocatorState> walker = new PrePostDescendentsWalker<ResolvingLocatorState>(TreeWalkPriority.VisualTree, ResolveLocatorPart, TerminateResolve, data); walker.StartWalk(startNode, skipStartNode); if (data.AttachmentLevel == AttachmentLevel.Full && data.AttachedAnchor != null) { // Merge the results with pre-existing selection if (selection != null) { SelectionProcessor selProcessor = GetSelectionProcessor(selection.GetType()); object newSelection; if (selProcessor != null) { if (selProcessor.MergeSelections(selection, data.AttachedAnchor, out newSelection)) { selection = newSelection; } else { // If we can't merge, them this locator isn't included in final results so we // we turn off the level that we are attempting to resolve attachmentLevel &= ~attemptedLevel; } } else { // If not selection processor, the locator can't be resolved so // we turn off the level that we were attempting to resolve attachmentLevel &= ~attemptedLevel; } } else { selection = data.AttachedAnchor; } } else { // ContentLocator didn't fully resolve so we turn off the level // that we were attempting to resolve attachmentLevel &= ~attemptedLevel; } return data; }