Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / MS / Internal / Data / XmlBindingWorker.cs / 2 / XmlBindingWorker.cs
//---------------------------------------------------------------------------- // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: Defines XmlBindingWorker object, workhorse for XML bindings // //--------------------------------------------------------------------------- using System; using System.Xml; using System.Xml.XPath; using System.Collections; using System.ComponentModel; using System.Reflection; using System.Windows.Threading; using System.Threading; using System.Diagnostics; using System.Windows; using System.Windows.Data; using System.Windows.Controls; // IGeneratorHost using MS.Internal.Data; namespace MS.Internal.Data { internal class XmlBindingWorker : BindingWorker, IWeakEventListener { private enum XPathType : byte { Default, SimpleName, SimpleAttribute } //----------------------------------------------------- // // Constructors // //----------------------------------------------------- internal XmlBindingWorker(ClrBindingWorker worker, bool collectionMode) : base(worker.ParentBindingExpression) { _hostWorker = worker; _xpath = ParentBinding.XPath; Debug.Assert(_xpath != null); // when collectionMode is true, we update the XmlDataCollection for XmlNodeChanges, // otherwise, any XmlNodeChange counts as a disastrous change that requires reset. _collectionMode = collectionMode; _xpathType = GetXPathType(_xpath); // PERF: it is possible to add one more optimization "mode" for the case when // we know the host wants to use the CurrentItem (i.e. DrillIn == Always). // We could be using SelectSingleNode() instead of SelectNodes(), // and then only watch for changes to one node instead of comparing collections. } //------------------------------------------------------ // // Internal Methods // //----------------------------------------------------- internal override void AttachDataItem() { // If there is an XPath, we get a context node for running queries by // creating a view from DataItem and using its CurrentItem. // If DataItem isn't a valid collection, it's probably an XmlNode, // in which case we will try using DataItem directly as the ContextNode if (XPath.Length > 0) { CollectionView = DataItem as CollectionView; if (CollectionView == null && DataItem is ICollection) { CollectionView = CollectionViewSource.GetDefaultCollectionView(DataItem, TargetElement); } } if (CollectionView != null) { CurrentChangedEventManager.AddListener(CollectionView, ParentBindingExpression); if (IsReflective) { CurrentChangingEventManager.AddListener(CollectionView, ParentBindingExpression); } } // Set ContextNode and hook events UpdateContextNode(true); } internal override void DetachDataItem() { //UnHook Collection Manager Currency notifications if (CollectionView != null) { CurrentChangedEventManager.RemoveListener(CollectionView, ParentBindingExpression); if (IsReflective) { CurrentChangingEventManager.RemoveListener(CollectionView, ParentBindingExpression); } CollectionView = null; } // Set ContextNode (this unhooks events first) UpdateContextNode(false); } internal override void OnCurrentChanged(ICollectionView collectionView, EventArgs args) { // There are two possible CurrentChanged events that comes through this event handler. // 1. CurrentChanged from DataItem as CollectionView // 2. CurrentChanged from QueriedCollection // only handle changed event from DataItem as CollectionView if (collectionView == CollectionView) { using (ParentBindingExpression.ChangingValue()) { // This will unhook and hook notifications UpdateContextNode(true); // tell host worker to use a new item _hostWorker.UseNewXmlItem(this.RawValue()); } } } internal override object RawValue() { if (XPath.Length == 0) { return DataItem; } if (ContextNode == null) // possibly because currentItem moved off collection { QueriedCollection = null; return null; } XmlNodeList nodes = SelectNodes(); if (nodes == null) { QueriedCollection = null; return DependencyProperty.UnsetValue; } return BuildQueriedCollection(nodes); } //------------------------------------------------------ // // Private Properties // //------------------------------------------------------ private XmlDataCollection QueriedCollection { get { return _queriedCollection; } set { _queriedCollection = value; } } private ICollectionView CollectionView { get { return _collectionView; } set { _collectionView = value; } } private XmlNode ContextNode { get { return _contextNode; } set { if (_contextNode != value && TraceData.IsExtendedTraceEnabled(ParentBindingExpression, TraceDataLevel.ReplaceItem)) { TraceData.Trace(TraceEventType.Warning, TraceData.XmlContextNode( TraceData.Identify(ParentBindingExpression), IdentifyNode(value))); } _contextNode = value; } } private string XPath { get { return _xpath; } } private XmlNamespaceManager NamespaceManager { get { DependencyObject target = TargetElement; if (target == null) return null; XmlNamespaceManager nsMgr = Binding.GetXmlNamespaceManager(target); if (nsMgr == null) { if (XmlDataProvider != null) { nsMgr = XmlDataProvider.XmlNamespaceManager; } } return nsMgr; } } // lazy computation of the XmlDataProvider that begat our data. // This is used primarily to get the right XmlNamespaceManager for // subqueries, sorting, etc. private XmlDataProvider XmlDataProvider { get { if (_xmlDataProvider == null) { XmlDataCollection xdc; // if the binding knows its data source and it's the right kind, use it if ((_xmlDataProvider = ParentBindingExpression.DataSource as XmlDataProvider) != null) { // nothing more to do } // if the data is an XmlDataCollection, use its provider else if ((xdc = DataItem as XmlDataCollection) != null) { _xmlDataProvider = xdc.ParentXmlDataProvider; } // if the data is a view over an XmlDataCollection, use its provider else if (CollectionView != null && (xdc = CollectionView.SourceCollection as XmlDataCollection) != null) { _xmlDataProvider = xdc.ParentXmlDataProvider; } // bindings in DataTemplates are typically bound to a single XmlNode. // Find the governing XmlDataProvider. else { _xmlDataProvider = Helper.XmlDataProviderForElement(TargetElement); } } return _xmlDataProvider; } } //----------------------------------------------------- // // Private Methods // //------------------------------------------------------ // Recalculate the node to be used as XPath query context; // only call this when CurrentItem changes and for Attach/Detach DataItem. // If worker was hooked up for notifications, this will UnhookNotifications() // before changing ContextNode, and then optionally HookNotifications() after. private void UpdateContextNode(bool hookNotifications) { UnHookNotifications(); if (CollectionView != null) { ContextNode = CollectionView.CurrentItem as XmlNode; if (ContextNode != CollectionView.CurrentItem && TraceData.IsEnabled) { TraceData.Trace(TraceEventType.Error, TraceData.XmlBindingToNonXmlCollection, XPath, ParentBindingExpression, DataItem); } } else { ContextNode = DataItem as XmlNode; if (ContextNode != DataItem && TraceData.IsEnabled) { TraceData.Trace(TraceEventType.Error, TraceData.XmlBindingToNonXml, XPath, ParentBindingExpression, DataItem); } } if (hookNotifications) HookNotifications(); } // We hook up only one set of event listeners per document and propagate // events to our worker instances. This is a perf savings because we use // a doubly-linked to add/remove workers, whereas delegate add/remove // is linear and doesn't scale well for high number of binding workers. private void HookNotifications() { //Hook Xml Node Change Notifications for one way //and two way binding if (IsDynamic) { // Check the node on which we would run XPath queries. // We can only hook if there is a node. if (ContextNode != null) { XmlDocument doc = DocumentFor(ContextNode); if (doc != null) { XmlNodeChangedEventManager.AddListener(doc, this); } } } } // see comment on HookNotifications() private void UnHookNotifications() { //Hook Xml Node Change Notifications for one way //and two way binding if (IsDynamic) { //this worker might not be hooked, either because //the query is empty or because of an invalid query. //Only unhook if we were hooked in the first place. if (ContextNode != null) { XmlDocument doc = DocumentFor(ContextNode); if (doc != null) { XmlNodeChangedEventManager.RemoveListener(doc, this); } } } } private XmlDocument DocumentFor(XmlNode node) { XmlDocument doc = node.OwnerDocument; if (doc == null) { // this may be a document itself doc = node as XmlDocument; } return doc; } XmlDataCollection BuildQueriedCollection(XmlNodeList nodes) { if (TraceData.IsExtendedTraceEnabled(ParentBindingExpression, TraceDataLevel.GetValue)) { TraceData.Trace(TraceEventType.Warning, TraceData.XmlNewCollection( TraceData.Identify(ParentBindingExpression), IdentifyNodeList(nodes))); } QueriedCollection = new XmlDataCollection(XmlDataProvider); QueriedCollection.XmlNamespaceManager = NamespaceManager; QueriedCollection.SynchronizeCollection(nodes); return QueriedCollection; } bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs args) { if (TraceData.IsExtendedTraceEnabled(ParentBindingExpression, TraceDataLevel.Events)) { TraceData.Trace(TraceEventType.Warning, TraceData.GotEvent( TraceData.Identify(ParentBindingExpression), TraceData.IdentifyWeakEvent(managerType), TraceData.Identify(sender))); } if (managerType == typeof(XmlNodeChangedEventManager)) { ProcessXmlNodeChanged(args); } else { return false; // unrecognized event } return true; } void ProcessXmlNodeChanged(EventArgs args) { // By the time this worker is notified, its binding's TargetElement may already be gone. // We should first check TargetElement to see if this worker still matters. (Fix 1494812) DependencyObject target = ParentBindingExpression.TargetElement; if (target == null) return; if (IgnoreSourcePropertyChange) return; // There should never be a change notification when there's no ContextNode. // If this Assert ever hits, something is wrong with the logic or ordering of // UpdateContextNode(), HookNotifications() and UnHookNotifications(). Debug.Assert(ContextNode != null); // ignore changes that cannot possibly affect the value of this XPath if (!IsChangeRelevant(args)) return; if (XPath.Length == 0) { // DataItem is being used directly; no need to check queries at all. _hostWorker.OnXmlValueChanged(); } else if (QueriedCollection == null) { // If there was no previous QueryCollection, it's probably because // the previous xpath query failed. Try again now. _hostWorker.UseNewXmlItem(this.RawValue()); } else { // We have a previous query result; run a new query for comparison: XmlNodeList nodes = SelectNodes(); if (nodes == null) { // Node change has caused the new query to fail. QueriedCollection = null; _hostWorker.UseNewXmlItem(DependencyProperty.UnsetValue); } else if (_collectionMode) { if (TraceData.IsExtendedTraceEnabled(ParentBindingExpression, TraceDataLevel.GetValue)) { TraceData.Trace(TraceEventType.Warning, TraceData.XmlSynchronizeCollection( TraceData.Identify(ParentBindingExpression), IdentifyNodeList(nodes))); } // Any xml change action, doesn't matter if it's an insert, // remove, or change, can result in any number of changes // to the content of the queried collection, so we have to // update the old collection with the new results. QueriedCollection.SynchronizeCollection(nodes); } // PERF: it is possible to add one more optimization "mode" here for singleMode. else if (QueriedCollection.CollectionHasChanged(nodes)) { // RawValue itself has changed, and we don't know // if the hostWorker is consuming information on the // collection itself or on its CurrentItem, so we just reset. _hostWorker.UseNewXmlItem(BuildQueriedCollection(nodes)); } else { // RawValue itself hasn't changed, but its children's content may have. _hostWorker.OnXmlValueChanged(); } } GC.KeepAlive(target); // keep target alive during process xml change (bug 1494812) } private XmlNodeList SelectNodes() { XmlNamespaceManager nsMgr = NamespaceManager; XmlNodeList nodes = null; try { if (nsMgr != null) { nodes = ContextNode.SelectNodes(XPath, nsMgr); } else { nodes = ContextNode.SelectNodes(XPath); } } catch (XPathException xe) { Status = BindingStatus.PathError; if (TraceData.IsEnabled) { TraceData.Trace(TraceEventType.Error, TraceData.CannotGetXmlNodeCollection, (ContextNode != null) ? ContextNode.Name : null, XPath, ParentBindingExpression, xe); } } if (TraceData.IsExtendedTraceEnabled(ParentBindingExpression, TraceDataLevel.GetValue)) { TraceData.Trace(TraceEventType.Warning, TraceData.SelectNodes( TraceData.Identify(ParentBindingExpression), IdentifyNode(ContextNode), TraceData.Identify(XPath), IdentifyNodeList(nodes))); } return nodes; } private string IdentifyNode(XmlNode node) { if (node == null) return ""; return String.Format(DataBindEngine.EnglishUSCulture, "{0} ({1})", node.GetType().Name, node.Name); } private string IdentifyNodeList(XmlNodeList nodeList) { if (nodeList == null) return " "; return String.Format(DataBindEngine.EnglishUSCulture, "{0} (hash={1} Count={2})", nodeList.GetType().Name, AvTrace.GetHashCodeHelper(nodeList), nodeList.Count); } // 90% of the XPaths used in practice are very simple - consisting of a // single child or attribute. For these we can streamline the process of // handling change events, since we can ignore many events that cannot // possibly affect the value of the XPath. private static XPathType GetXPathType(string xpath) { int n = xpath.Length; if (n == 0) return XPathType.SimpleName; // attributes start with '@', followed by a Name bool isAttribute = (xpath[0] == '@'); int index = isAttribute ? 1 : 0; if (index >= n) return XPathType.Default; // [XML spec] Name ::= (Letter | '_' | ':') (NameChar)* char c = xpath[index]; if (! (Char.IsLetter(c) || c == '_' || c == ':') ) return XPathType.Default; // [XML spec] NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender // We ignore the last two possibilities to keep the code simple. They // don't arise often in practice. for (++index; index < n; ++index) { c = xpath[index]; if (! (Char.IsLetterOrDigit(c) || c == '.' || c == '-' || c == '_' || c == ':') ) return XPathType.Default; } return isAttribute ? XPathType.SimpleAttribute : XPathType.SimpleName; } // determine if a change can possibly affect the value of the XPath private bool IsChangeRelevant(EventArgs rawArgs) { // if the XPath isn't "simple", any change in the XML tree might // affect its value if (_xpathType == XPathType.Default) return true; XmlNodeChangedEventArgs args = (XmlNodeChangedEventArgs)rawArgs; XmlNode parent = null; XmlNode valueNode = null; switch (args.Action) { case XmlNodeChangedAction.Insert: parent = args.NewParent; break; case XmlNodeChangedAction.Remove: parent = args.OldParent; break; case XmlNodeChangedAction.Change: valueNode = args.Node; break; } if (_collectionMode) { // only insertions/deletions to the context node are relevant return (parent == ContextNode); } else { // insertions/deletions to the context node are relevant - // the inserted/deleted node might match the XPath if (parent == ContextNode) return true; // also relevant are changes that affect the value of the result // node. This includes value changes directly on the result node, // as well as any change to the descendants of the result node. XmlNode resultNode = _hostWorker.GetResultNode() as XmlNode; if (resultNode == null) return false; if (valueNode != null) parent = valueNode; while (parent != null) { if (parent == resultNode) return true; parent = parent.ParentNode; } return false; } } //----------------------------------------------------- // // Private Fields // //----------------------------------------------------- private bool _collectionMode; private XPathType _xpathType; private XmlNode _contextNode; private XmlDataCollection _queriedCollection; // new DataCollection private ICollectionView _collectionView; private XmlDataProvider _xmlDataProvider; private ClrBindingWorker _hostWorker; private string _xpath; } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // // Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: Defines XmlBindingWorker object, workhorse for XML bindings // //--------------------------------------------------------------------------- using System; using System.Xml; using System.Xml.XPath; using System.Collections; using System.ComponentModel; using System.Reflection; using System.Windows.Threading; using System.Threading; using System.Diagnostics; using System.Windows; using System.Windows.Data; using System.Windows.Controls; // IGeneratorHost using MS.Internal.Data; namespace MS.Internal.Data { internal class XmlBindingWorker : BindingWorker, IWeakEventListener { private enum XPathType : byte { Default, SimpleName, SimpleAttribute } //----------------------------------------------------- // // Constructors // //----------------------------------------------------- internal XmlBindingWorker(ClrBindingWorker worker, bool collectionMode) : base(worker.ParentBindingExpression) { _hostWorker = worker; _xpath = ParentBinding.XPath; Debug.Assert(_xpath != null); // when collectionMode is true, we update the XmlDataCollection for XmlNodeChanges, // otherwise, any XmlNodeChange counts as a disastrous change that requires reset. _collectionMode = collectionMode; _xpathType = GetXPathType(_xpath); // PERF: it is possible to add one more optimization "mode" for the case when // we know the host wants to use the CurrentItem (i.e. DrillIn == Always). // We could be using SelectSingleNode() instead of SelectNodes(), // and then only watch for changes to one node instead of comparing collections. } //------------------------------------------------------ // // Internal Methods // //----------------------------------------------------- internal override void AttachDataItem() { // If there is an XPath, we get a context node for running queries by // creating a view from DataItem and using its CurrentItem. // If DataItem isn't a valid collection, it's probably an XmlNode, // in which case we will try using DataItem directly as the ContextNode if (XPath.Length > 0) { CollectionView = DataItem as CollectionView; if (CollectionView == null && DataItem is ICollection) { CollectionView = CollectionViewSource.GetDefaultCollectionView(DataItem, TargetElement); } } if (CollectionView != null) { CurrentChangedEventManager.AddListener(CollectionView, ParentBindingExpression); if (IsReflective) { CurrentChangingEventManager.AddListener(CollectionView, ParentBindingExpression); } } // Set ContextNode and hook events UpdateContextNode(true); } internal override void DetachDataItem() { //UnHook Collection Manager Currency notifications if (CollectionView != null) { CurrentChangedEventManager.RemoveListener(CollectionView, ParentBindingExpression); if (IsReflective) { CurrentChangingEventManager.RemoveListener(CollectionView, ParentBindingExpression); } CollectionView = null; } // Set ContextNode (this unhooks events first) UpdateContextNode(false); } internal override void OnCurrentChanged(ICollectionView collectionView, EventArgs args) { // There are two possible CurrentChanged events that comes through this event handler. // 1. CurrentChanged from DataItem as CollectionView // 2. CurrentChanged from QueriedCollection // only handle changed event from DataItem as CollectionView if (collectionView == CollectionView) { using (ParentBindingExpression.ChangingValue()) { // This will unhook and hook notifications UpdateContextNode(true); // tell host worker to use a new item _hostWorker.UseNewXmlItem(this.RawValue()); } } } internal override object RawValue() { if (XPath.Length == 0) { return DataItem; } if (ContextNode == null) // possibly because currentItem moved off collection { QueriedCollection = null; return null; } XmlNodeList nodes = SelectNodes(); if (nodes == null) { QueriedCollection = null; return DependencyProperty.UnsetValue; } return BuildQueriedCollection(nodes); } //------------------------------------------------------ // // Private Properties // //------------------------------------------------------ private XmlDataCollection QueriedCollection { get { return _queriedCollection; } set { _queriedCollection = value; } } private ICollectionView CollectionView { get { return _collectionView; } set { _collectionView = value; } } private XmlNode ContextNode { get { return _contextNode; } set { if (_contextNode != value && TraceData.IsExtendedTraceEnabled(ParentBindingExpression, TraceDataLevel.ReplaceItem)) { TraceData.Trace(TraceEventType.Warning, TraceData.XmlContextNode( TraceData.Identify(ParentBindingExpression), IdentifyNode(value))); } _contextNode = value; } } private string XPath { get { return _xpath; } } private XmlNamespaceManager NamespaceManager { get { DependencyObject target = TargetElement; if (target == null) return null; XmlNamespaceManager nsMgr = Binding.GetXmlNamespaceManager(target); if (nsMgr == null) { if (XmlDataProvider != null) { nsMgr = XmlDataProvider.XmlNamespaceManager; } } return nsMgr; } } // lazy computation of the XmlDataProvider that begat our data. // This is used primarily to get the right XmlNamespaceManager for // subqueries, sorting, etc. private XmlDataProvider XmlDataProvider { get { if (_xmlDataProvider == null) { XmlDataCollection xdc; // if the binding knows its data source and it's the right kind, use it if ((_xmlDataProvider = ParentBindingExpression.DataSource as XmlDataProvider) != null) { // nothing more to do } // if the data is an XmlDataCollection, use its provider else if ((xdc = DataItem as XmlDataCollection) != null) { _xmlDataProvider = xdc.ParentXmlDataProvider; } // if the data is a view over an XmlDataCollection, use its provider else if (CollectionView != null && (xdc = CollectionView.SourceCollection as XmlDataCollection) != null) { _xmlDataProvider = xdc.ParentXmlDataProvider; } // bindings in DataTemplates are typically bound to a single XmlNode. // Find the governing XmlDataProvider. else { _xmlDataProvider = Helper.XmlDataProviderForElement(TargetElement); } } return _xmlDataProvider; } } //----------------------------------------------------- // // Private Methods // //------------------------------------------------------ // Recalculate the node to be used as XPath query context; // only call this when CurrentItem changes and for Attach/Detach DataItem. // If worker was hooked up for notifications, this will UnhookNotifications() // before changing ContextNode, and then optionally HookNotifications() after. private void UpdateContextNode(bool hookNotifications) { UnHookNotifications(); if (CollectionView != null) { ContextNode = CollectionView.CurrentItem as XmlNode; if (ContextNode != CollectionView.CurrentItem && TraceData.IsEnabled) { TraceData.Trace(TraceEventType.Error, TraceData.XmlBindingToNonXmlCollection, XPath, ParentBindingExpression, DataItem); } } else { ContextNode = DataItem as XmlNode; if (ContextNode != DataItem && TraceData.IsEnabled) { TraceData.Trace(TraceEventType.Error, TraceData.XmlBindingToNonXml, XPath, ParentBindingExpression, DataItem); } } if (hookNotifications) HookNotifications(); } // We hook up only one set of event listeners per document and propagate // events to our worker instances. This is a perf savings because we use // a doubly-linked to add/remove workers, whereas delegate add/remove // is linear and doesn't scale well for high number of binding workers. private void HookNotifications() { //Hook Xml Node Change Notifications for one way //and two way binding if (IsDynamic) { // Check the node on which we would run XPath queries. // We can only hook if there is a node. if (ContextNode != null) { XmlDocument doc = DocumentFor(ContextNode); if (doc != null) { XmlNodeChangedEventManager.AddListener(doc, this); } } } } // see comment on HookNotifications() private void UnHookNotifications() { //Hook Xml Node Change Notifications for one way //and two way binding if (IsDynamic) { //this worker might not be hooked, either because //the query is empty or because of an invalid query. //Only unhook if we were hooked in the first place. if (ContextNode != null) { XmlDocument doc = DocumentFor(ContextNode); if (doc != null) { XmlNodeChangedEventManager.RemoveListener(doc, this); } } } } private XmlDocument DocumentFor(XmlNode node) { XmlDocument doc = node.OwnerDocument; if (doc == null) { // this may be a document itself doc = node as XmlDocument; } return doc; } XmlDataCollection BuildQueriedCollection(XmlNodeList nodes) { if (TraceData.IsExtendedTraceEnabled(ParentBindingExpression, TraceDataLevel.GetValue)) { TraceData.Trace(TraceEventType.Warning, TraceData.XmlNewCollection( TraceData.Identify(ParentBindingExpression), IdentifyNodeList(nodes))); } QueriedCollection = new XmlDataCollection(XmlDataProvider); QueriedCollection.XmlNamespaceManager = NamespaceManager; QueriedCollection.SynchronizeCollection(nodes); return QueriedCollection; } bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs args) { if (TraceData.IsExtendedTraceEnabled(ParentBindingExpression, TraceDataLevel.Events)) { TraceData.Trace(TraceEventType.Warning, TraceData.GotEvent( TraceData.Identify(ParentBindingExpression), TraceData.IdentifyWeakEvent(managerType), TraceData.Identify(sender))); } if (managerType == typeof(XmlNodeChangedEventManager)) { ProcessXmlNodeChanged(args); } else { return false; // unrecognized event } return true; } void ProcessXmlNodeChanged(EventArgs args) { // By the time this worker is notified, its binding's TargetElement may already be gone. // We should first check TargetElement to see if this worker still matters. (Fix 1494812) DependencyObject target = ParentBindingExpression.TargetElement; if (target == null) return; if (IgnoreSourcePropertyChange) return; // There should never be a change notification when there's no ContextNode. // If this Assert ever hits, something is wrong with the logic or ordering of // UpdateContextNode(), HookNotifications() and UnHookNotifications(). Debug.Assert(ContextNode != null); // ignore changes that cannot possibly affect the value of this XPath if (!IsChangeRelevant(args)) return; if (XPath.Length == 0) { // DataItem is being used directly; no need to check queries at all. _hostWorker.OnXmlValueChanged(); } else if (QueriedCollection == null) { // If there was no previous QueryCollection, it's probably because // the previous xpath query failed. Try again now. _hostWorker.UseNewXmlItem(this.RawValue()); } else { // We have a previous query result; run a new query for comparison: XmlNodeList nodes = SelectNodes(); if (nodes == null) { // Node change has caused the new query to fail. QueriedCollection = null; _hostWorker.UseNewXmlItem(DependencyProperty.UnsetValue); } else if (_collectionMode) { if (TraceData.IsExtendedTraceEnabled(ParentBindingExpression, TraceDataLevel.GetValue)) { TraceData.Trace(TraceEventType.Warning, TraceData.XmlSynchronizeCollection( TraceData.Identify(ParentBindingExpression), IdentifyNodeList(nodes))); } // Any xml change action, doesn't matter if it's an insert, // remove, or change, can result in any number of changes // to the content of the queried collection, so we have to // update the old collection with the new results. QueriedCollection.SynchronizeCollection(nodes); } // PERF: it is possible to add one more optimization "mode" here for singleMode. else if (QueriedCollection.CollectionHasChanged(nodes)) { // RawValue itself has changed, and we don't know // if the hostWorker is consuming information on the // collection itself or on its CurrentItem, so we just reset. _hostWorker.UseNewXmlItem(BuildQueriedCollection(nodes)); } else { // RawValue itself hasn't changed, but its children's content may have. _hostWorker.OnXmlValueChanged(); } } GC.KeepAlive(target); // keep target alive during process xml change (bug 1494812) } private XmlNodeList SelectNodes() { XmlNamespaceManager nsMgr = NamespaceManager; XmlNodeList nodes = null; try { if (nsMgr != null) { nodes = ContextNode.SelectNodes(XPath, nsMgr); } else { nodes = ContextNode.SelectNodes(XPath); } } catch (XPathException xe) { Status = BindingStatus.PathError; if (TraceData.IsEnabled) { TraceData.Trace(TraceEventType.Error, TraceData.CannotGetXmlNodeCollection, (ContextNode != null) ? ContextNode.Name : null, XPath, ParentBindingExpression, xe); } } if (TraceData.IsExtendedTraceEnabled(ParentBindingExpression, TraceDataLevel.GetValue)) { TraceData.Trace(TraceEventType.Warning, TraceData.SelectNodes( TraceData.Identify(ParentBindingExpression), IdentifyNode(ContextNode), TraceData.Identify(XPath), IdentifyNodeList(nodes))); } return nodes; } private string IdentifyNode(XmlNode node) { if (node == null) return ""; return String.Format(DataBindEngine.EnglishUSCulture, "{0} ({1})", node.GetType().Name, node.Name); } private string IdentifyNodeList(XmlNodeList nodeList) { if (nodeList == null) return " "; return String.Format(DataBindEngine.EnglishUSCulture, "{0} (hash={1} Count={2})", nodeList.GetType().Name, AvTrace.GetHashCodeHelper(nodeList), nodeList.Count); } // 90% of the XPaths used in practice are very simple - consisting of a // single child or attribute. For these we can streamline the process of // handling change events, since we can ignore many events that cannot // possibly affect the value of the XPath. private static XPathType GetXPathType(string xpath) { int n = xpath.Length; if (n == 0) return XPathType.SimpleName; // attributes start with '@', followed by a Name bool isAttribute = (xpath[0] == '@'); int index = isAttribute ? 1 : 0; if (index >= n) return XPathType.Default; // [XML spec] Name ::= (Letter | '_' | ':') (NameChar)* char c = xpath[index]; if (! (Char.IsLetter(c) || c == '_' || c == ':') ) return XPathType.Default; // [XML spec] NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender // We ignore the last two possibilities to keep the code simple. They // don't arise often in practice. for (++index; index < n; ++index) { c = xpath[index]; if (! (Char.IsLetterOrDigit(c) || c == '.' || c == '-' || c == '_' || c == ':') ) return XPathType.Default; } return isAttribute ? XPathType.SimpleAttribute : XPathType.SimpleName; } // determine if a change can possibly affect the value of the XPath private bool IsChangeRelevant(EventArgs rawArgs) { // if the XPath isn't "simple", any change in the XML tree might // affect its value if (_xpathType == XPathType.Default) return true; XmlNodeChangedEventArgs args = (XmlNodeChangedEventArgs)rawArgs; XmlNode parent = null; XmlNode valueNode = null; switch (args.Action) { case XmlNodeChangedAction.Insert: parent = args.NewParent; break; case XmlNodeChangedAction.Remove: parent = args.OldParent; break; case XmlNodeChangedAction.Change: valueNode = args.Node; break; } if (_collectionMode) { // only insertions/deletions to the context node are relevant return (parent == ContextNode); } else { // insertions/deletions to the context node are relevant - // the inserted/deleted node might match the XPath if (parent == ContextNode) return true; // also relevant are changes that affect the value of the result // node. This includes value changes directly on the result node, // as well as any change to the descendants of the result node. XmlNode resultNode = _hostWorker.GetResultNode() as XmlNode; if (resultNode == null) return false; if (valueNode != null) parent = valueNode; while (parent != null) { if (parent == resultNode) return true; parent = parent.ParentNode; } return false; } } //----------------------------------------------------- // // Private Fields // //----------------------------------------------------- private bool _collectionMode; private XPathType _xpathType; private XmlNode _contextNode; private XmlDataCollection _queriedCollection; // new DataCollection private ICollectionView _collectionView; private XmlDataProvider _xmlDataProvider; private ClrBindingWorker _hostWorker; private string _xpath; } } // 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
- JpegBitmapDecoder.cs
- StateItem.cs
- EntryWrittenEventArgs.cs
- SecurityManager.cs
- SingleKeyFrameCollection.cs
- DocumentsTrace.cs
- FixUp.cs
- DesignerTransaction.cs
- ZipIOLocalFileHeader.cs
- DragSelectionMessageFilter.cs
- SiteIdentityPermission.cs
- SchemaConstraints.cs
- XmlNavigatorFilter.cs
- ViewEvent.cs
- FixedTextSelectionProcessor.cs
- Variant.cs
- HostingEnvironment.cs
- UserMapPath.cs
- GetWinFXPath.cs
- WorkflowDesignerMessageFilter.cs
- SafeBitVector32.cs
- EqualityComparer.cs
- DataContractJsonSerializerOperationFormatter.cs
- WarningException.cs
- GridViewUpdateEventArgs.cs
- ToolStripRenderer.cs
- UserControlDesigner.cs
- InkCanvasSelectionAdorner.cs
- Preprocessor.cs
- FragmentNavigationEventArgs.cs
- ControlIdConverter.cs
- DataBindingCollection.cs
- StringResourceManager.cs
- WindowsListBox.cs
- PagerSettings.cs
- GetImportedCardRequest.cs
- RegexNode.cs
- ControlValuePropertyAttribute.cs
- Iis7Helper.cs
- WindowsProgressbar.cs
- Crc32.cs
- MarshalDirectiveException.cs
- RoleBoolean.cs
- StateMachine.cs
- NodeFunctions.cs
- CodeRegionDirective.cs
- ListBoxItemAutomationPeer.cs
- TimeZone.cs
- EventEntry.cs
- Point3D.cs
- SqlDataSourceSelectingEventArgs.cs
- XsltContext.cs
- ErrorProvider.cs
- ClipboardProcessor.cs
- DataGrid.cs
- PixelShader.cs
- HtmlInputSubmit.cs
- SecurityChannelListener.cs
- TableLayoutPanelResizeGlyph.cs
- ButtonFlatAdapter.cs
- TimerElapsedEvenArgs.cs
- ChangesetResponse.cs
- DataBindingCollection.cs
- MappingSource.cs
- HttpCookie.cs
- URLIdentityPermission.cs
- RegisteredArrayDeclaration.cs
- FileSystemInfo.cs
- TemplatedAdorner.cs
- ScaleTransform.cs
- FormViewPageEventArgs.cs
- SmiEventSink_DeferedProcessing.cs
- XmlQueryOutput.cs
- FunctionDescription.cs
- NameValueCollection.cs
- HtmlTitle.cs
- ContentFilePart.cs
- Module.cs
- ExpressionEvaluator.cs
- ActivityExecutor.cs
- ExpressionsCollectionConverter.cs
- BinaryMessageEncodingElement.cs
- MachineSettingsSection.cs
- RsaSecurityTokenAuthenticator.cs
- TextDecorationLocationValidation.cs
- MissingMemberException.cs
- MouseBinding.cs
- NameObjectCollectionBase.cs
- RuntimeVariableList.cs
- ArgIterator.cs
- OneWayChannelListener.cs
- InheritanceService.cs
- DbDataRecord.cs
- FieldNameLookup.cs
- RemotingSurrogateSelector.cs
- Visual3D.cs
- CodeGeneratorOptions.cs
- TypeBuilder.cs
- ListControl.cs
- Polygon.cs