Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / Orcas / QFE / wpf / src / Framework / System / Windows / Markup / OptimizedTemplateContent.cs / 1 / OptimizedTemplateContent.cs
/****************************************************************************\ * * File: OptimizedTemplateContent.cs * * Purpose: Represents a FrameworkTemplate's content * in an optimized form (separates the shareable parts of the * template from those which need to be re-instantiated on * every use). * * Copyright (C) 2005 by Microsoft Corporation. All rights reserved. * \***************************************************************************/ using System; using System.Xml; using System.IO; using System.Windows; using System.Windows.Media; using System.Windows.Navigation; using System.Text; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media.Animation; using System.Diagnostics; using System.Reflection; using System.Windows.Threading; using System.Windows.Data; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Globalization; using MS.Utility; namespace System.Windows.Markup { /***************************************************************************\ * * ShareableRecordState * * States in the sealing state machine. Used to identify what we are currently * processing in ReadRecord. * \***************************************************************************/ internal enum ShareableRecordState { // Initial state, or we don't know what the next state will be Unset, // The current record is unshareable and should be added to the unshareable list UnshareableRecord, // We are currently within an unshareable subtree. We will exit this subtree // by watching the depth of complex property tags. All records in an // unshareable subtree go into the unshared list UnshareableSubtree, // The current record is a shareable dependency property, and should be added // to the list of shared properties ShareableRecord, // Starting the instantiation of a possibly shared subtree under a complex // property. This may end up being an unshared tree if certain conditions // are encountered. StartShareableTree, // In the process of building a possibly shared subtree under a complex // dependency property. BuildingShareableTree, } /***************************************************************************\ * * BamlSubtreeState * * Used by the OptimizedTemplateContentHelper to tell the OptimizedTemplateContent * the outcome of processing the last baml record. This can in turn be used * to modify the current ShareableRecordState. * \***************************************************************************/ internal enum BamlSubtreeState { // Record or state encountered which means the subtree can't be shared NotShareable, // Successful end of a subtree that can be shared has been reached EndShareableSubtree, // Continuing to build subtree. So far, so good. ContinueShareableSubtree, } internal class OptimizedTemplateContent { #region Constructor internal OptimizedTemplateContent( FrameworkTemplate frameworkTemplate ) { _frameworkTemplate = frameworkTemplate; _currentState = ShareableRecordState.Unset; _searchingForName = false; _sharedProperties = null; _unsharedContentCollection = null; _unsharedContent = null; } #endregion Constructor #region Internals //+------------------------------------------------------------------- // // BeginReadingContent // // Called by the TemplateBamlRecordReader before it starts reading // the content portion of the template. After this, call AddContentRecord // multiple times, then EndReadingContent. // //+-------------------------------------------------------------------- internal void BeginReadingContent( ParserContext parserContext ) { // We shouldn't have been here before Debug.Assert( _unsharedContent == null ); // Keep a copy of the parser context until EndReadingContent _parserContext = parserContext; // Initialize all the temporary buffers we need Debug.Assert( _sharedProperties == null ); _sharedProperties = new FrugalObjectList(16); Debug.Assert( _unsharedContentCollection == null ); _unsharedContentCollection = new Collection (); Debug.Assert( _optimizableElementState == null ); _optimizableElementState = new Stack (); Debug.Assert( _optimizedTemplateContentHelper == null ); _optimizedTemplateContentHelper = new OptimizedTemplateContentHelper( parserContext ); Debug.Assert( _nameStack == null ); _nameStack = new Stack(); } //+------------------------------------------------------------------- // // AddContentRecord // // Called by the TemplateBamlRecordReader when it is reading the content // portion of the template. Call BeginReadingContent before this, and // EndReadingContent afterwards. // //+-------------------------------------------------------------------- internal void AddContentRecord(BamlRecord bamlRecord ) { if( _rootType == null ) { BamlElementStartRecord bamlElementStartRecord = bamlRecord as BamlElementStartRecord; _rootType = MapTable.GetTypeFromId(bamlElementStartRecord.TypeId); } ReadRecord( bamlRecord ); } internal void AddBamlRecordToUnsharedContentCollection(BamlRecord bamlRecord) { _unsharedContentCollection.Add(bamlRecord); // if this record has a Debug extension in it's Next field than install that also. if (BamlRecordHelper.HasDebugExtensionRecord(_parserContext.IsDebugBamlStream, bamlRecord)) { _unsharedContentCollection.Add(bamlRecord.Next); } } //+-------------------------------------------------------------------- // // EndReadingContent // // Called by the TemplateBamlRecordReader after it's read all the content. // //+------------------------------------------------------------------- internal void EndReadingContent() { // Prefetch the values for static resources within the template content _frameworkTemplate.StaticResourceValues = PrefetchStaticResourceValues(); // Link all of the unshareable content (that's how we walk through it // when instantiating). for( int i = 0; i < _unsharedContentCollection.Count-1; i++ ) { _unsharedContentCollection[i].Next = _unsharedContentCollection[i+1]; } if( _unsharedContentCollection.Count > 0 ) { _unsharedContentCollection[_unsharedContentCollection.Count-1].Next = null; } // We're done processing the records, so we're done with // the parser context and the helper objects. _typeConvertContext = null; // (From the parser context) _parserContext = null; _optimizedTemplateContentHelper = null; _optimizableElementState = null; _nameStack = null; #if DEBUG for (int j = 0; j < _unsharedContentCollection.Count; j++) { Debug.Assert( _unsharedContentCollection[j].IsPinned ); } #endif // Since the records are linked, we can just hold on to the first // record, and free the list. Debug.Assert( _unsharedContent == null ); _unsharedContent = _unsharedContentCollection[0]; _unsharedContentCollection = null; // Another thing we could potentially do here is process the shared values, // actually update the tables like StyleHelper.ProcessTemplateContent, // instead of waiting for the template to be sealed. That's more complicated // code, though, and doesn't appear to be a benefit. } //+-------------------------------------------------------------------------- // // PrefetchStaticResourceValues // // Prefetch the staticresource values wrt to the context available at // template creation time. // //+------------------------------------------------------------------------- private object[] PrefetchStaticResourceValues() { object[] staticResourceValues = null; if (_parserContext.InDeferredSection) { // Example: // // // // // If we are within deferred section all we need to // do is re-resolve the front loaded static resources staticResourceValues = _parserContext.StaticResourcesStack[_parserContext.StaticResourcesStack.Count - 1]; // Find the indices of the first and the last StaticResourceId // occuring within this template content. In above example this // would be indices 1 - 2. short startId = -1; short endId = -1; for ( int i = 0; i < _unsharedContentCollection.Count; i++ ) { BamlRecord bamlRecord = _unsharedContentCollection[i]; BamlRecordType recordType = bamlRecord.RecordType; if (recordType == BamlRecordType.StaticResourceId || recordType == BamlRecordType.PropertyWithStaticResourceId) { endId = ((BamlStaticResourceIdRecord)bamlRecord).StaticResourceId; if (startId == -1) { startId = endId; } } } // Re-resolve the staticresources that belong to this template content and // fallback to the pre-fetched value if not resolved within the current context. if (startId >= 0) { Debug.Assert(endId >= 0 && endId >= startId && endId < staticResourceValues.Length); for ( int i = startId; i <= endId; i++ ) { // Note that we do not want to search the app or theme because this is // only a re-resolution within the current context stack. The top-level // deferred section is the only case when we lookup the app and theme // dictionaries. object value = _parserContext.BamlReader.FindResourceInParserStack( ((DeferredResourceReference)staticResourceValues[i]).Key, true /*allowDeferredResourceReference*/, true /*mustReturnDeferredResourceReference*/); if (value != DependencyProperty.UnsetValue) { // If the resource was found replace the existing // entry in the list of pre-fetched values staticResourceValues[i] = value; } } } } else { // Example: // // // Find all the StaticResource records in the unshareable content // Notice that in the above scenario we only care about the static resources // in the template content and the front-loaded static resources within the // deferable content. The StaticResourcesIds need not be altered. ArrayList staticResourceValuesList = null; BamlRecordReader bamlReader = null; for ( int i = 0; i < _unsharedContentCollection.Count; i++ ) { // Look for a StaticResource ElementStart record BamlRecord bamlRecord = _unsharedContentCollection[i]; BamlRecordType recordType = bamlRecord.RecordType; if (recordType == BamlRecordType.ElementStart || recordType == BamlRecordType.StaticResourceStart) { int positiveTypeId = -((BamlElementStartRecord)bamlRecord).TypeId; if (positiveTypeId == (int)KnownElements.StaticResourceExtension) { // Find the StaticResource ElementEnd record int elementDepth = 1; BamlRecord srStartRecord = bamlRecord; do { // Keep removing the baml records that belong to the current // static resource from the unshared list _unsharedContentCollection.RemoveAt(i); // Link records in the current static resource together bamlRecord.Next = _unsharedContentCollection[i]; bamlRecord = _unsharedContentCollection[i]; recordType = bamlRecord.RecordType; if (recordType == BamlRecordType.ElementStart || recordType == BamlRecordType.StaticResourceStart) { elementDepth++; } else if (recordType == BamlRecordType.ElementEnd || recordType == BamlRecordType.StaticResourceEnd) { elementDepth--; } } while (elementDepth > 0); _unsharedContentCollection.RemoveAt(i); bamlRecord.Next = null; // Ensure that we have a BamlReader and a StaticResourceValuesList EnsureBamlReaderAndStaticResourceValuesList(ref staticResourceValuesList, ref bamlReader); // Load the StaticResource records bamlReader.PreParsedRecordsStart = srStartRecord; bamlReader.PreParsedCurrentRecord = srStartRecord; bamlReader.Read(); StaticResourceExtension staticResource = (StaticResourceExtension)bamlReader.RootList[0]; bamlReader.RootList.Clear(); // Resolve the StaticResource value including lookup to the app and the theme DeferredResourceReference value = (DeferredResourceReference)bamlReader.PreviousBamlRecordReader.FindResourceInParentChain( staticResource.ResourceKey, true /*allowDeferredResourceReference*/, true /*mustReturnDeferredResourceReference*/); // Add the resolved value to the static resource list staticResourceValuesList.Add(value); // Replace the static resource records in this unshared // list with a StaticResourceIdRecord. BamlStaticResourceIdRecord bamlStaticResourceIdRecord = new BamlStaticResourceIdRecord(); bamlStaticResourceIdRecord.StaticResourceId = (short)(staticResourceValuesList.Count - 1); bamlStaticResourceIdRecord.Pin(); _unsharedContentCollection.Insert(i, bamlStaticResourceIdRecord); } } else if (recordType == BamlRecordType.PropertyWithExtension || recordType == BamlRecordType.OptimizedStaticResource) { IOptimizedMarkupExtension optimizedMarkupExtensionRecord = (IOptimizedMarkupExtension)bamlRecord; int positiveTypeId = optimizedMarkupExtensionRecord.ExtensionTypeId; if (positiveTypeId == (int)KnownElements.StaticResourceExtension) { // Remove the baml record with the StaticResource // from the unshared list _unsharedContentCollection.RemoveAt(i); bamlRecord.Next = null; // Ensure that we have a BamlReader and a StaticResourceValuesList EnsureBamlReaderAndStaticResourceValuesList(ref staticResourceValuesList, ref bamlReader); // Load the StaticResource StaticResourceExtension staticResource = (StaticResourceExtension)bamlReader.GetExtensionValue(optimizedMarkupExtensionRecord, null); // Resolve the StaticResource value including lookup to the app and the theme DeferredResourceReference value = (DeferredResourceReference)bamlReader.PreviousBamlRecordReader.FindResourceInParentChain( staticResource.ResourceKey, true /*allowDeferredResourceReference*/, true /*mustReturnDeferredResourceReference*/); // Add the resolved value to the static resource list staticResourceValuesList.Add(value); BamlStaticResourceIdRecord bamlStaticResourceIdRecord; if (recordType == BamlRecordType.PropertyWithExtension) { // Replace the PropertyWithExtensionRecord in this unshared // list with a PropertyWithStaticResourceIdRecord. BamlPropertyWithStaticResourceIdRecord bamlPropertyWithStaticResourceIdRecord = new BamlPropertyWithStaticResourceIdRecord(); bamlPropertyWithStaticResourceIdRecord.AttributeId = ((BamlPropertyWithExtensionRecord)bamlRecord).AttributeId; bamlStaticResourceIdRecord = bamlPropertyWithStaticResourceIdRecord; } else { // Replace the OptimizedStaticResourceRecord in this unshared // list with a StaticResourceIdRecord. bamlStaticResourceIdRecord = new BamlStaticResourceIdRecord(); } bamlStaticResourceIdRecord.StaticResourceId = (short)(staticResourceValuesList.Count - 1); bamlStaticResourceIdRecord.Pin(); _unsharedContentCollection.Insert(i, bamlStaticResourceIdRecord); } } } if (staticResourceValuesList != null) { // Restore the reader on the ParserContext _parserContext.BamlReader = bamlReader.PreviousBamlRecordReader; staticResourceValues = staticResourceValuesList.ToArray(); } } return staticResourceValues; } //+------------------------------------------------------------------------- // // EnsureBamlReaderAndStaticResourceValuesList // // Ensure that we have a BamlReader and a StaticResourceCaluesList to work with // //+------------------------------------------------------------------------- private void EnsureBamlReaderAndStaticResourceValuesList( ref ArrayList staticResourceValuesList, ref BamlRecordReader bamlReader) { if (staticResourceValuesList == null) { // This is the first StaticResource we found // within the template content section staticResourceValuesList = new ArrayList(); bamlReader = new BamlRecordReader(); bamlReader.SetPreviousBamlRecordReader(_parserContext.BamlReader); bamlReader.ParserContext = _parserContext; bamlReader.RootList = new ArrayList(1); } } //+-------------------------------------------------------------------------- // // ReadRecord // // Read a single record and process it. A record is added to the // _unsharedContentCollection, or the value is added to _sharedProperties. // //+------------------------------------------------------------------------- private void ReadRecord(BamlRecord bamlRecord) { // First keep the _optimizableElementState up-to-date. This is used to // tell us if the current element is an FE or FCE (if it is, // we can use the optimizations). CheckElementStartForOptimization( bamlRecord ); // Some record types should have been filtered out before the baml record // collection was created by the TemplateBamlRecordReader if (bamlRecord.RecordType == BamlRecordType.PIMapping || bamlRecord.RecordType == BamlRecordType.AssemblyInfo || bamlRecord.RecordType == BamlRecordType.TypeInfo || bamlRecord.RecordType == BamlRecordType.TypeSerializerInfo || bamlRecord.RecordType == BamlRecordType.AttributeInfo || bamlRecord.RecordType == BamlRecordType.StringInfo || bamlRecord.RecordType == BamlRecordType.DocumentStart || bamlRecord.RecordType == BamlRecordType.DocumentEnd) { // Enum.ToString(culture) is [Obsolete] #pragma warning disable 0618 ThrowException(SRID.TemplateInvalidBamlRecord, bamlRecord.RecordType.ToString(XamlReaderHelper.EnglishUSCulture)); #pragma warning restore 0618 } // Are we in the middle of a property value that we know can't // be shared? If so, add this record to the _unsharedContentCollection list. if (_currentState == ShareableRecordState.UnshareableSubtree) { AddBamlRecordToUnsharedContentCollection(bamlRecord); // Also, keep our tree-depth counters up-to-date. TrackComplexPropertyTreeDepth(bamlRecord); // If we reached the end of the property value that couldn't be shared, then reset // state (maybe the next property value will be shareable). if( CurrentIsOptimizableElement() ) { _currentState = ShareableRecordState.Unset; } } // Or are we in the middle of processing a set of records that is // *shareable*? else if (_currentState == ShareableRecordState.BuildingShareableTree) { // We are currently instantiating a possibly shared subtree, so pass // off the baml record to the record reader for processing and // instantiation. Note that the record reader will perform another // set of checks to determine if it needs to bail out of shareable // subtree processing and go back to being an unshareable subtree. ReadSharedRecord( bamlRecord ); } // If we fall to this else, we're not in a shareable tree or an unshareable tree; // we don't know what state to be in. We need to look at this record and figure // out where to put it (it may be the start of a shareable/unshareable tree). else { ReadPotentiallyShareableRecord( bamlRecord ); } } //+------------------------------------------------------------------------------------------------------ // // ReadSharedRecord // // This is called by ReadRecord, when we're processing baml records in the template content. It's // called when we're in a tree of records that will be a shared property. Note, though, that we // may stop sharing during this routine, if the record turns out to be un-shareable. // //+------------------------------------------------------------------------------------------------------ private void ReadSharedRecord( BamlRecord bamlRecord ) { // We are currently instantiating a possibly shared subtree, so pass // off the baml record to the record reader for processing and // instantiation. Note that the record reader will perform another // set of checks to determine if it needs to bail out of shareable // subtree processing and go back to being an unshareable subtree. // If that is the case, change status to UnshareableSubtree and copy // the portion of the subtree read so far into the unshareable list. BamlSubtreeState status = _optimizedTemplateContentHelper.ReadSubtreeRecord(bamlRecord); // Did the _optimizedTemplateContentHelper just decide that this sub-tree // isn't shareable after all? if (status == BamlSubtreeState.NotShareable) { // Yes. Reset our current state, copy any // records that we have already passed over into the _unsharedContentCollection // and continue on UnwindSharedRecords( bamlRecord ); _currentState = ShareableRecordState.Unset; //ShareableRecordState.UnshareableSubtree; } // Or, we're still in a shareable subtree, but maybe this // is the end of the subtree? else if (status == BamlSubtreeState.EndShareableSubtree) { // Yes, the subtree is shareable and we've reached the end of the tree. // Update the Dp value in the shared list. SharedDp sdp = _sharedProperties[_sharedProperties.Count-1]; sdp.Value = _optimizedTemplateContentHelper.RootList[0]; // Freezable freezableValue; System.Windows.Data.CollectionViewSource collectionViewSource; bool canShare = true; // This value is intended to be shared by all instances of the template. So freeze it, // call ME.ProvideValue on it, etc. StyleHelper.ProcessSharedPropertyValue( _parserContext, sdp, sdp.Dp, ref sdp.Value ); // We may not actually be able to share this value, though. In // that case, we'll have to release this instance. So figure out // that now. if (sdp.Value == null) { canShare = true; } else { Type valueType = sdp.Value.GetType(); // First, is this a shareable type? canShare = IsShareableType(valueType); // Second, some types are potentially shareable, but we have to inspect the instance to // know for sure. if (canShare) { if ((freezableValue = sdp.Value as Freezable) != null) { canShare = freezableValue.CanFreeze; } else if ((collectionViewSource = sdp.Value as System.Windows.Data.CollectionViewSource) != null) { canShare = collectionViewSource.IsShareableInTemplate(); } } // Finally, the only MEs we should have at this point are // binding and dynamic resources; we already called ProvideValue, // so any other ME isn't something we know about. if( canShare && valueType.IsAssignableFrom(typeof(MarkupExtension))) { if( !valueType.IsAssignableFrom(typeof(BindingBase)) && !valueType.IsAssignableFrom(typeof(TemplateBindingExtension)) && !valueType.IsAssignableFrom(typeof(DynamicResourceExtension)) ) { canShare = false; } } } // If it can't be shared, put the records into the unshared list. if( !canShare ) { UnwindSharedRecords( bamlRecord ); } else { // This can be shared, so we don't need to keep this BamlRecord in memory any more. while( _bamlRecordsSubtreeStart != null ) { BamlRecord nextRecord = _bamlRecordsSubtreeStart.Next; _bamlRecordsSubtreeStart.Unpin(); // Since we're not pinning this record any more, we're essentially // returning it to the cache, so we want to clear any Next reference. // But if it's still pinned (i.e., it's part of a nested template), // then we can't modify it. if( _bamlRecordsSubtreeStart.PinnedCount == 0 ) { _bamlRecordsSubtreeStart.Next = null; } if( _bamlRecordsSubtreeStart == bamlRecord ) _bamlRecordsSubtreeStart = null; else _bamlRecordsSubtreeStart = nextRecord; } } // Reset all the status trackers. _optimizedTemplateContentHelper.Reset(); _currentState = ShareableRecordState.Unset; } } //+--------------------------------------------------------------------- // // UnwindSharedRecords // // Remove the last property value from the _sharedProperty collection, // because, as it turns out, it's not shareable. Put all the Baml // records for that property into the list of baml records for // unshareable content (_unsharedContentCollection). // //+---------------------------------------------------------------------- private void UnwindSharedRecords( BamlRecord bamlRecord ) { Debug.Assert( _optimizableElementState.Count >= _positionOptimizableElementState ); // Update the _optimizableElementState back to where it was when we // started processing this property value that we thought // was going to be shared. while( _optimizableElementState.Count > _positionOptimizableElementState ) { _optimizableElementState.Pop(); } // Remove the value from the shared properties table _sharedProperties.RemoveAt(_sharedProperties.Count - 1); // Get the baml records for this property value and put them // into _unsharedContentCollection (the first baml records // for this value is pointed to be _bamlRecordsSubtreeStart). // (This code overlaps AddContentRecord, should be a better // way to share). while( _bamlRecordsSubtreeStart != null ) { BamlRecord bamlRecordT = _bamlRecordsSubtreeStart; CheckElementStartForOptimization( bamlRecordT ); CheckForName( bamlRecordT); _unsharedContentCollection.Add(bamlRecordT); if( _bamlRecordsSubtreeStart == bamlRecord ) _bamlRecordsSubtreeStart = null; else _bamlRecordsSubtreeStart = _bamlRecordsSubtreeStart.Next; } _optimizedTemplateContentHelper.Reset(); } //+------------------------------------------------------------------------------------------------- // // ReadPotentiallyShareableRecord // // This is called by ReadRecord, when we're processing baml records in the template content. It's // called when we don't know if this record should be shared or not. // //+------------------------------------------------------------------------------------------------- private void ReadPotentiallyShareableRecord( BamlRecord bamlRecord ) { DependencyProperty dp; object dpValue; // Check whether this record is shareable or not. If it is // shareable and is a DependencyProperty of some type, then // store it to be added to the template's shared value list. _currentState = LookForShareableRecord(bamlRecord, out dp, out dpValue); // Check if we need an x:Name associated with the current element or // not. This is dependent on the current state. Note that during this // call, we could get a new/replacement bamlRecord back. CheckForName( bamlRecord); // If this is an unshareable record, add it to the list. if (_currentState == ShareableRecordState.UnshareableRecord || _currentState == ShareableRecordState.UnshareableSubtree) { AddBamlRecordToUnsharedContentCollection(bamlRecord); } // Or, maybe we're starting a (potentially) shareable subtree else if (_currentState == ShareableRecordState.StartShareableTree) { // If we get to here we are starting a shared (or possibly unshared) // subtree. In this case remember where this subtree starts, in case // we need to unwind. OnStartShareableTree( bamlRecord ); // We're now in a shareable subtree mode _currentState = ShareableRecordState.BuildingShareableTree; // Remember the current dp. This may be removed if we later determine // this is not a shareable subtree. _sharedProperties.Add(new SharedDp(dp, null, CurrentElementName)); } else if (_currentState == ShareableRecordState.ShareableRecord) { // The call to GetShareableRecordState above actually figured out // the value for us. First, freeze it (if it's a Freezable), since // we don't handle changes to shared values. StyleHelper.ProcessSharedPropertyValue( _parserContext, _frameworkTemplate, dp, ref dpValue ); // Then store the property until the end of the start // tag, at which point we should have the x:Name needed to add // all the shared properties to the templates shared list. Shared // properties that don't have the element name set yet are indicated by // having a null name. if (String.IsNullOrEmpty(CurrentElementName)) _sharedProperties.Add(new SharedDp(dp, dpValue, null)); else _sharedProperties.Add(new SharedDp(dp, dpValue, CurrentElementName)); // Let this record be recycled by the BamlRecordManager's cache. bamlRecord.Next = null; bamlRecord.Unpin(); } } //+----------------------------------------------------------------------- // // OnStartShareableTree // // This remembers some state as we start processing baml records // for a potentially shareable property value; if it turns out not to // be shareable, we'll use the fields set here to unwind and put those // baml records into the unshareable collection. // //+------------------------------------------------------------------------ private void OnStartShareableTree( BamlRecord bamlRecord ) { // Remember the first baml records _bamlRecordsSubtreeStart = bamlRecord; _positionOptimizableElementState = _optimizableElementState.Count; } //+--------------------------------------------------------------------------- // // CheckElementStartForOptimization // // This is called by ReadRecord, and is used to keep the _optimizableElementState // up-to-date. This is used to tell us if the current element is an FE or FCE // (if it is, we can use the optimizations). // //+---------------------------------------------------------------------------- // These flags keep track of whether we're on an optimizable element, // i.e. one that will become a template child. It also tracks if we're // on or under a name scope. [Flags] private enum OptimizableElementState { Optimizable = 1, // An FE or FCE, and not in a nested name scope OnNestedNameScope = 2, // E.g. this element implements INameScope WhithinNestedNamescope = 4 // Has an INameScope as an ancestor } private void CheckElementStartForOptimization( BamlRecord bamlRecord ) { OptimizableElementState newState = 0; // Initialize newState if( _optimizableElementState.Count > 0 ) { OptimizableElementState previousState = _optimizableElementState.Peek(); // If we were at or within a namescope before, we're within one now. if( (previousState & (OptimizableElementState.WhithinNestedNamescope | OptimizableElementState.OnNestedNameScope)) != 0 ) { newState = OptimizableElementState.WhithinNestedNamescope; } } switch( bamlRecord.RecordType ) { case BamlRecordType.PropertyArrayStart: case BamlRecordType.PropertyIListStart: case BamlRecordType.PropertyIDictionaryStart: /* case BamlRecordType.PropertyComplexStart: case BamlRecordType.Text: */ // If we're in a property element, we're basically not // in an FE or FCE any more (we won't be doing any shared // value optimizations). _optimizableElementState.Push( newState ); break; case BamlRecordType.ElementStart: // If newState was set above, we don't need to update it. if( newState == 0) { BamlElementStartRecord bamlElementStartRecord = bamlRecord as BamlElementStartRecord; Type elementType = MapTable.GetTypeFromId( bamlElementStartRecord.TypeId ); // If this is an IComponentConnector, then we know that is a name scope. // If this is an IComponentConnector/DO, we assume it is a name scope. This is // slightly over-restrictive, but as close as we can get, while still allowing // for the common UserControl scenario. if( typeof(IComponentConnector).IsAssignableFrom(elementType) && typeof(DependencyObject).IsAssignableFrom(elementType) || typeof(INameScope).IsAssignableFrom(elementType) ) { newState = OptimizableElementState.OnNestedNameScope; } // If this is an FE or FCE, it is optimizable. Note that this means // that an element can be optimizable (a template child) at a namescope, but // not below one. if( KnownTypes.Types[(int)KnownElements.FrameworkElement].IsAssignableFrom( elementType) || KnownTypes.Types[(int)KnownElements.FrameworkContentElement].IsAssignableFrom( elementType ) ) { newState |= OptimizableElementState.Optimizable; } } _optimizableElementState.Push( newState ); break; case BamlRecordType.ElementEnd: case BamlRecordType.PropertyArrayEnd: case BamlRecordType.PropertyIListEnd: case BamlRecordType.PropertyIDictionaryEnd: // Pop the above stack. _optimizableElementState.Pop(); break; } } //+-------------------------------------------------------------------------------------- // // TrackComplexPropertyTreeDepth // // Determine where we are in a subtree within a complex property. This is // used to determine when to stop processing a shared or unshared subtree. // //+------------------------------------------------------------------------------------- private void TrackComplexPropertyTreeDepth(BamlRecord bamlRecord) { } //+-------------------------------------------------------------------------------------- // // CheckForName // // Look for a name property within the template content records. If we find // one, we know we can't share the record, and for FE/FCE elements we can // update the shared values type for this element. // //+------------------------------------------------------------------------------------- private void CheckForName( BamlRecord bamlRecord ) { switch (bamlRecord.RecordType) { case BamlRecordType.ElementStart: // If we were searching for a name on the last record, we can stop // searching now, we're not going to find one. if( _searchingForName ) { _searchingForName = false; // Update the shared values table to keep the generated name. UpdateSharedPropertyNames(); } { BamlNamedElementStartRecord bamlNamedElementStartRecord = bamlRecord as BamlNamedElementStartRecord; bool inFeOrFce = CurrentIsOptimizableElement(); // If this is an FE/FCE, name it (this temp name might get replaced later, // if the content actually has one). if( inFeOrFce ) { // Generate a name. We'll use an illegal name, because we're past name validation at this // point, and that guarantees that we won't have a conflict (it's invalid because it begins // with a numeric). string newName = (_generatedNameIndex++).ToString(CultureInfo.InvariantCulture) + "_T"; // Indicate that this is a temp name, and we're still searching for a real name _searchingForName = true; // Put this name on the stack and into the record. _nameStack.Push( newName ); bamlNamedElementStartRecord.RuntimeName = newName; // Also set a bit on the record indicating that this is a template child. // This is used during template application as a validation step to ensure // that child elements in a template do not implement a run-time name scope. bamlNamedElementStartRecord.IsTemplateChild = true; } else { // We're not in an FE/FCE, but keep the stack consistent. _nameStack.Push( String.Empty ); } } break; case BamlRecordType.ElementEnd: // If we were searching for a name on the last record, we can stop // searching now, we're not going to find one. if( _searchingForName ) { _searchingForName = false; UpdateSharedPropertyNames(); } // [....] the name stack. _nameStack.Pop(); break; case BamlRecordType.Property: case BamlRecordType.PropertyWithConverter: // See if we found the x:Name attribute BamlPropertyRecord propertyRecord = bamlRecord as BamlPropertyRecord; if (_searchingForName && MapTable.DoesAttributeMatch(propertyRecord.AttributeId, BamlAttributeUsage.RuntimeName)) { // Yes, this is the runtime name // Look in the unshareable content for the generated name, and replace it with the real name. for( int i = _unsharedContentCollection.Count - 1; i >= 0; --i ) { if (_unsharedContentCollection[i].RecordType == BamlRecordType.ElementStart) { BamlNamedElementStartRecord bamlNamedElementStartRecord = _unsharedContentCollection[i] as BamlNamedElementStartRecord; if( bamlNamedElementStartRecord.RuntimeName != null ) { bamlNamedElementStartRecord.RuntimeName = propertyRecord.Value; break; } } } // Correct the name stack _nameStack.Pop(); _nameStack.Push( propertyRecord.Value ); // Correct the naming in the shared values table _searchingForName = false; // This must be set before calling UpdateSharedPropertyNames UpdateSharedPropertyNames(); } break; case BamlRecordType.PropertyTypeReference: case BamlRecordType.PropertyStringReference: case BamlRecordType.PropertyCustom: case BamlRecordType.PropertyWithExtension: break; case BamlRecordType.PropertyComplexStart: case BamlRecordType.PropertyArrayStart: case BamlRecordType.PropertyIListStart: case BamlRecordType.PropertyIDictionaryStart: case BamlRecordType.Text: // If we were searching for a name on the last record, we can stop // searching now, we're not going to find one. if( _searchingForName ) { _searchingForName = false; UpdateSharedPropertyNames(); } break; case BamlRecordType.DefAttribute: BamlDefAttributeRecord defAttributeRecord = bamlRecord as BamlDefAttributeRecord; if (defAttributeRecord.NameId == BamlMapTable.NameStringId && _searchingForName) { // Look in the unshareable content for the generated name, and replace it with the real name. for( int i = _unsharedContentCollection.Count - 1; i >= 0; --i ) { if (_unsharedContentCollection[i].RecordType == BamlRecordType.ElementStart) { BamlNamedElementStartRecord bamlNamedElementStartRecord = _unsharedContentCollection[i] as BamlNamedElementStartRecord; if( bamlNamedElementStartRecord.RuntimeName != null ) { bamlNamedElementStartRecord.RuntimeName = defAttributeRecord.Name; bamlRecord = null; break; } } } // Correct the name stack _nameStack.Pop(); _nameStack.Push( defAttributeRecord.Name ); // Correct the shared property names table _searchingForName = false; // This must be set before calling UpdateSharedPropertyNames UpdateSharedPropertyNames(); } break; case BamlRecordType.RoutedEvent: case BamlRecordType.ClrEvent: // Should never reach here Debug.Assert( false, "Shouldn't see events at this point in the template code" ); break; default: break; } } //+----------------------------------------------------------------------------------------- // // UpdateSharedList // // Update the last items on the shared DependencyProperty list with the // name of the current element. // //+----------------------------------------------------------------------------------------- private void UpdateSharedPropertyNames() { if (_nameStack.Count > 0) { // Get the name of the current element string name = CurrentElementName; #if DEBUG int lastIndex = _frameworkTemplate._lastChildIndex; int childIndex = #endif // Generate an index for this name StyleHelper.CreateChildIndexFromChildName(CurrentElementName, _frameworkTemplate); #if DEBUG Debug.Assert( childIndex == lastIndex ); #endif // The tail of the _sharedProperties list has the properties for this element, // all with no name. Fill in the name now. for (int i = _sharedProperties.Count - 1; i >= 0; i--) { SharedDp sdp = _sharedProperties[i]; if (sdp.ElementName == null) { sdp.ElementName = name; } else { break; } } } } //+------------------------------------------------------------------ // // LookForShareableRecord // // This is called when we don't know whether or not a record can // be shared. E.g. it's not called when we know we're in a shareable // or un-shareable tree already. // // If the returned value is ShareableRecord, then dp and dpValue will // hold the value to be shared. // // Properties are shareable if they're a DP, on an FE, and the property // type is shareable (e.g. a Freezable that can't be frozen isn't // shareable). // //+----------------------------------------------------------------- private ShareableRecordState LookForShareableRecord( BamlRecord bamlRecord, out DependencyProperty dp, out object dpValue) { dp = null; dpValue = null; bool isFeOrFce = CurrentIsOptimizableElement(); // We'll assume that this isn't shareable. ShareableRecordState status = ShareableRecordState.UnshareableRecord; switch (bamlRecord.RecordType) { // Simple properties have 5 variations: // Property that contains just a string to be converted using a typeconverter // reflected for at runtime // PropertyWithConverter that contains a string and the typeid for the converter // PropertyTypeReference contains a typeid for a property that has a Type value // PropertyStringReference contains a string id for properties that are strings // PropertyCustom contains a custom binary representation of a property case BamlRecordType.Property: if (isFeOrFce) { BamlPropertyRecord bamlPropertyRecord = bamlRecord as BamlPropertyRecord; if (ParseDependencyProperty(bamlPropertyRecord.Value, bamlPropertyRecord.AttributeId, 0, out dp, out dpValue)) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } } break; case BamlRecordType.PropertyWithConverter: if( isFeOrFce ) { BamlPropertyWithConverterRecord bamlPropertyConverterRecord = bamlRecord as BamlPropertyWithConverterRecord; if (ParseDependencyProperty(bamlPropertyConverterRecord.Value, bamlPropertyConverterRecord.AttributeId, bamlPropertyConverterRecord.ConverterTypeId, out dp, out dpValue)) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } } break; case BamlRecordType.PropertyTypeReference: if( isFeOrFce ) { BamlPropertyTypeReferenceRecord bamlPropertyTypeRecord = bamlRecord as BamlPropertyTypeReferenceRecord; Type valueType = MapTable.GetTypeFromId(bamlPropertyTypeRecord.TypeId); dp = MapTable.GetDependencyProperty(bamlPropertyTypeRecord.AttributeId); if (dp != null) { dpValue = valueType; // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } } break; case BamlRecordType.PropertyStringReference: if( isFeOrFce ) { BamlPropertyStringReferenceRecord bamlPropertyStringRecord = bamlRecord as BamlPropertyStringReferenceRecord; string attribValue = _optimizedTemplateContentHelper.GetPropertyValueFromStringId(bamlPropertyStringRecord.StringId); if (ParseDependencyProperty(attribValue, bamlPropertyStringRecord.AttributeId, 0, out dp, out dpValue)) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } } break; case BamlRecordType.PropertyCustom: BamlPropertyCustomRecord bamlPropertyCustomRecord = bamlRecord as BamlPropertyCustomRecord; if (ReadCustomProperty(bamlPropertyCustomRecord, out dp, out dpValue) && isFeOrFce) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } break; case BamlRecordType.PropertyWithExtension: if (isFeOrFce) { BamlPropertyWithExtensionRecord bamlPropertyRecord = bamlRecord as BamlPropertyWithExtensionRecord; if (ReadPropertyWithExtension(bamlPropertyRecord, out dp, out dpValue)) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } } break; // If this is a property element (complex property), we'll try to share it. If it's // some kind of collection, we don't support sharing for it; sharing for collection properties // gets confusing, because some people expect merge semantics rather than replacement semantics. case BamlRecordType.PropertyComplexStart: if (isFeOrFce) { BamlPropertyComplexStartRecord propertyComplexStartRecord = bamlRecord as BamlPropertyComplexStartRecord; dp = MapTable.GetDependencyProperty(propertyComplexStartRecord.AttributeId); if (dp != null) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.StartShareableTree; } } break; } return status; } internal bool ReadPropertyWithExtension(BamlPropertyWithExtensionRecord bamlPropertyRecord, out DependencyProperty dp, out object propertyValue) { short attributeId = bamlPropertyRecord.AttributeId; dp = MapTable.GetDependencyProperty(attributeId); propertyValue = null; // Not shareable if not a DP if (dp == null) { return false; } propertyValue = _optimizedTemplateContentHelper.GetExtensionValue(bamlPropertyRecord, dp.Name); // MarkupExtensions are always shareable except for StaticResourceExtension. bool isShareable = (bamlPropertyRecord.ExtensionTypeId != (short)KnownElements.StaticResourceExtension); return isShareable; } internal bool ReadCustomProperty(BamlPropertyCustomRecord bamlPropertyCustomRecord, out DependencyProperty dp, out object propertyValue) { bool isShareable = false; if (bamlPropertyCustomRecord.SerializerTypeId == (short)KnownElements.DependencyPropertyConverter) { dp = null; propertyValue = _optimizedTemplateContentHelper.GetCustomDependencyPropertyValue(bamlPropertyCustomRecord); } else { string propName = null; Type propType = null; short attributeId = bamlPropertyCustomRecord.AttributeId; dp = MapTable.GetDependencyProperty(attributeId); if (dp == null) { propType = MapTable.GetCLRPropertyTypeAndNameFromId(attributeId, out propName); } else { propType = dp.PropertyType; propName = dp.Name; } // Read the custom property value into the record. propertyValue = _optimizedTemplateContentHelper.GetCustomValue(bamlPropertyCustomRecord, propType, propName); // Not shareable if not a DP if (dp != null) { isShareable = IsShareable(propertyValue, propType); } } return isShareable; } private bool IsShareable(object propValue, Type propType) { // Not shareable if if this is a Visual or ContentElement if (!IsShareableType(propType)) { return false; } // Not shareable if this is a Freezable that cannot be frozen. Freezable freezable = propValue as Freezable; if (freezable != null && !freezable.CanFreeze) { return false; } // Otherwise assume it can be shared. return true; } //+-------------------------------------------------------------------------------------- // // ParseDependencyPropeprty // // Process a single property value. Return true if it is a DependencyProperty // with a non-null value that is shareable. Return false if this property // is not shareable for some reason. // //+-------------------------------------------------------------------------------------- internal bool ParseDependencyProperty( string attribValue, short attributeId, short converterTypeId, out DependencyProperty dp, out object propertyValue) { Debug.Assert( CurrentIsOptimizableElement() ); // Figure out the DP from the attribute ID dp = MapTable.GetDependencyProperty(attributeId); propertyValue = null; // If we didn't find a DP, it's not shareable if (dp == null) { return false; } // Get the value for the DependencyProperty. propertyValue = XamlTypeMapper.ParseProperty(null, dp.PropertyType, dp.Name, dp, TypeConvertContext, _parserContext, attribValue, converterTypeId); return IsShareable(propertyValue, propertyValue.GetType()); } // IsShareableType // // See if a type is at least potentially shareabl across // applications of a template. // static internal bool IsShareableType( Type t ) { if( // We handle Freezables on an per-instance basis. KnownTypes.Types[(int)KnownElements.Freezable].IsAssignableFrom(t) || // Well-known immutable CLR types t == typeof(string) || t == typeof(Uri) || t == typeof(Type) || // We assume MEs are shareable; the host object is responsible // for ensuring immutability. The exception is static resource // references, for which we have special support in templates. (typeof(MarkupExtension).IsAssignableFrom(t) && !typeof(StaticResourceExtension).IsAssignableFrom(t)) || // Styles & Templates are mostly shareable typeof(Style).IsAssignableFrom(t) || typeof(FrameworkTemplate).IsAssignableFrom(t) || // CVS might be shareable, wait for the instance check typeof(CollectionViewSource).IsAssignableFrom(t) || // Value types are immutable by nature t.IsValueType ) { return true; } else { return false; } } // // ReleaseSharedProperties // // Null out the _sharedProperties. This is called during seal, // once we've pulled everything out of the shared properties that we need. // So free some heap space. // internal void ReleaseSharedProperties() { _sharedProperties = null; } #endregion Internals #region Helpers //+------------------------------------------------------------------------------------- // // OptimizedTemplateContent helper methods // //+-------------------------------------------------------------------------------------- // Throw an exception with one parameter private void ThrowException( string id, string parameter1) { string message = SR.Get(id, parameter1); //throw new XamlParseException(message); XamlParseException.ThrowException( _parserContext, _parserContext.LineNumber, _parserContext.LinePosition, message, null ); } // The BamlMapTable from the ParserContext internal BamlMapTable MapTable { get { // We keep this alive even after the template content // has been optimized, because we need it during // instantiation. if (_mapTable == null) { _mapTable = _parserContext.MapTable; } return _mapTable; } } // See if this optimized template is empty internal bool IsEmpty { get { return _unsharedContent == null && ( _sharedProperties == null || _sharedProperties.Count == 0 ); } } // The XamlTypeMapper from the ParserContext private XamlTypeMapper XamlTypeMapper { get { return _parserContext.XamlTypeMapper; } } // Wrapper to tell if the current element is an FE or an FCE // (using the _optimizableElementState) private bool CurrentIsOptimizableElement() { return _optimizableElementState.Count != 0 && (_optimizableElementState.Peek() & OptimizableElementState.Optimizable) != 0; } // Generate a TypeConvertContext private TypeConvertContext TypeConvertContext { get { if (null == _typeConvertContext) { _typeConvertContext = new TypeConvertContext(_parserContext); } return _typeConvertContext; } } // Get the name of the current element private string CurrentElementName { get { // No names exist yet? if (_nameStack.Count == 0) return null; // Are we still searching for a name? // (If so, ignore the generated name on the stack) else if( _searchingForName ) return null; // Otherwise, return the real name from the stack. else return _nameStack.Peek() as String; } } // The Baml records that have to be re-instantiated every time. internal BamlRecord UnsharedContent { get { return _unsharedContent; } } // The property values that can be shared between instantiations // of the template. internal FrugalObjectListSharedProperties { get { return _sharedProperties; } } // Type of the template content's root element (used for validation). internal Type RootType { get { return _rootType; } } #endregion Helpers #region Data private ParserContext _parserContext; private Stack _optimizableElementState; private BamlMapTable _mapTable; private OptimizedTemplateContentHelper _optimizedTemplateContentHelper; private FrameworkTemplate _frameworkTemplate; private ShareableRecordState _currentState; private bool _searchingForName; private BamlRecord _unsharedContent; private FrugalObjectList _sharedProperties; private Stack _nameStack; private Collection _unsharedContentCollection; private TypeConvertContext _typeConvertContext; private BamlRecord _bamlRecordsSubtreeStart; private Type _rootType; // Type of the content's root element private int _positionOptimizableElementState; // Used by StartShreableTree private int _generatedNameIndex = 0; #endregion Data } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. /****************************************************************************\ * * File: OptimizedTemplateContent.cs * * Purpose: Represents a FrameworkTemplate's content * in an optimized form (separates the shareable parts of the * template from those which need to be re-instantiated on * every use). * * Copyright (C) 2005 by Microsoft Corporation. All rights reserved. * \***************************************************************************/ using System; using System.Xml; using System.IO; using System.Windows; using System.Windows.Media; using System.Windows.Navigation; using System.Text; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media.Animation; using System.Diagnostics; using System.Reflection; using System.Windows.Threading; using System.Windows.Data; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Globalization; using MS.Utility; namespace System.Windows.Markup { /***************************************************************************\ * * ShareableRecordState * * States in the sealing state machine. Used to identify what we are currently * processing in ReadRecord. * \***************************************************************************/ internal enum ShareableRecordState { // Initial state, or we don't know what the next state will be Unset, // The current record is unshareable and should be added to the unshareable list UnshareableRecord, // We are currently within an unshareable subtree. We will exit this subtree // by watching the depth of complex property tags. All records in an // unshareable subtree go into the unshared list UnshareableSubtree, // The current record is a shareable dependency property, and should be added // to the list of shared properties ShareableRecord, // Starting the instantiation of a possibly shared subtree under a complex // property. This may end up being an unshared tree if certain conditions // are encountered. StartShareableTree, // In the process of building a possibly shared subtree under a complex // dependency property. BuildingShareableTree, } /***************************************************************************\ * * BamlSubtreeState * * Used by the OptimizedTemplateContentHelper to tell the OptimizedTemplateContent * the outcome of processing the last baml record. This can in turn be used * to modify the current ShareableRecordState. * \***************************************************************************/ internal enum BamlSubtreeState { // Record or state encountered which means the subtree can't be shared NotShareable, // Successful end of a subtree that can be shared has been reached EndShareableSubtree, // Continuing to build subtree. So far, so good. ContinueShareableSubtree, } internal class OptimizedTemplateContent { #region Constructor internal OptimizedTemplateContent( FrameworkTemplate frameworkTemplate ) { _frameworkTemplate = frameworkTemplate; _currentState = ShareableRecordState.Unset; _searchingForName = false; _sharedProperties = null; _unsharedContentCollection = null; _unsharedContent = null; } #endregion Constructor #region Internals //+------------------------------------------------------------------- // // BeginReadingContent // // Called by the TemplateBamlRecordReader before it starts reading // the content portion of the template. After this, call AddContentRecord // multiple times, then EndReadingContent. // //+-------------------------------------------------------------------- internal void BeginReadingContent( ParserContext parserContext ) { // We shouldn't have been here before Debug.Assert( _unsharedContent == null ); // Keep a copy of the parser context until EndReadingContent _parserContext = parserContext; // Initialize all the temporary buffers we need Debug.Assert( _sharedProperties == null ); _sharedProperties = new FrugalObjectList (16); Debug.Assert( _unsharedContentCollection == null ); _unsharedContentCollection = new Collection (); Debug.Assert( _optimizableElementState == null ); _optimizableElementState = new Stack (); Debug.Assert( _optimizedTemplateContentHelper == null ); _optimizedTemplateContentHelper = new OptimizedTemplateContentHelper( parserContext ); Debug.Assert( _nameStack == null ); _nameStack = new Stack(); } //+------------------------------------------------------------------- // // AddContentRecord // // Called by the TemplateBamlRecordReader when it is reading the content // portion of the template. Call BeginReadingContent before this, and // EndReadingContent afterwards. // //+-------------------------------------------------------------------- internal void AddContentRecord(BamlRecord bamlRecord ) { if( _rootType == null ) { BamlElementStartRecord bamlElementStartRecord = bamlRecord as BamlElementStartRecord; _rootType = MapTable.GetTypeFromId(bamlElementStartRecord.TypeId); } ReadRecord( bamlRecord ); } internal void AddBamlRecordToUnsharedContentCollection(BamlRecord bamlRecord) { _unsharedContentCollection.Add(bamlRecord); // if this record has a Debug extension in it's Next field than install that also. if (BamlRecordHelper.HasDebugExtensionRecord(_parserContext.IsDebugBamlStream, bamlRecord)) { _unsharedContentCollection.Add(bamlRecord.Next); } } //+-------------------------------------------------------------------- // // EndReadingContent // // Called by the TemplateBamlRecordReader after it's read all the content. // //+------------------------------------------------------------------- internal void EndReadingContent() { // Prefetch the values for static resources within the template content _frameworkTemplate.StaticResourceValues = PrefetchStaticResourceValues(); // Link all of the unshareable content (that's how we walk through it // when instantiating). for( int i = 0; i < _unsharedContentCollection.Count-1; i++ ) { _unsharedContentCollection[i].Next = _unsharedContentCollection[i+1]; } if( _unsharedContentCollection.Count > 0 ) { _unsharedContentCollection[_unsharedContentCollection.Count-1].Next = null; } // We're done processing the records, so we're done with // the parser context and the helper objects. _typeConvertContext = null; // (From the parser context) _parserContext = null; _optimizedTemplateContentHelper = null; _optimizableElementState = null; _nameStack = null; #if DEBUG for (int j = 0; j < _unsharedContentCollection.Count; j++) { Debug.Assert( _unsharedContentCollection[j].IsPinned ); } #endif // Since the records are linked, we can just hold on to the first // record, and free the list. Debug.Assert( _unsharedContent == null ); _unsharedContent = _unsharedContentCollection[0]; _unsharedContentCollection = null; // Another thing we could potentially do here is process the shared values, // actually update the tables like StyleHelper.ProcessTemplateContent, // instead of waiting for the template to be sealed. That's more complicated // code, though, and doesn't appear to be a benefit. } //+-------------------------------------------------------------------------- // // PrefetchStaticResourceValues // // Prefetch the staticresource values wrt to the context available at // template creation time. // //+------------------------------------------------------------------------- private object[] PrefetchStaticResourceValues() { object[] staticResourceValues = null; if (_parserContext.InDeferredSection) { // Example: // // // // // If we are within deferred section all we need to // do is re-resolve the front loaded static resources staticResourceValues = _parserContext.StaticResourcesStack[_parserContext.StaticResourcesStack.Count - 1]; // Find the indices of the first and the last StaticResourceId // occuring within this template content. In above example this // would be indices 1 - 2. short startId = -1; short endId = -1; for ( int i = 0; i < _unsharedContentCollection.Count; i++ ) { BamlRecord bamlRecord = _unsharedContentCollection[i]; BamlRecordType recordType = bamlRecord.RecordType; if (recordType == BamlRecordType.StaticResourceId || recordType == BamlRecordType.PropertyWithStaticResourceId) { endId = ((BamlStaticResourceIdRecord)bamlRecord).StaticResourceId; if (startId == -1) { startId = endId; } } } // Re-resolve the staticresources that belong to this template content and // fallback to the pre-fetched value if not resolved within the current context. if (startId >= 0) { Debug.Assert(endId >= 0 && endId >= startId && endId < staticResourceValues.Length); for ( int i = startId; i <= endId; i++ ) { // Note that we do not want to search the app or theme because this is // only a re-resolution within the current context stack. The top-level // deferred section is the only case when we lookup the app and theme // dictionaries. object value = _parserContext.BamlReader.FindResourceInParserStack( ((DeferredResourceReference)staticResourceValues[i]).Key, true /*allowDeferredResourceReference*/, true /*mustReturnDeferredResourceReference*/); if (value != DependencyProperty.UnsetValue) { // If the resource was found replace the existing // entry in the list of pre-fetched values staticResourceValues[i] = value; } } } } else { // Example: // // // Find all the StaticResource records in the unshareable content // Notice that in the above scenario we only care about the static resources // in the template content and the front-loaded static resources within the // deferable content. The StaticResourcesIds need not be altered. ArrayList staticResourceValuesList = null; BamlRecordReader bamlReader = null; for ( int i = 0; i < _unsharedContentCollection.Count; i++ ) { // Look for a StaticResource ElementStart record BamlRecord bamlRecord = _unsharedContentCollection[i]; BamlRecordType recordType = bamlRecord.RecordType; if (recordType == BamlRecordType.ElementStart || recordType == BamlRecordType.StaticResourceStart) { int positiveTypeId = -((BamlElementStartRecord)bamlRecord).TypeId; if (positiveTypeId == (int)KnownElements.StaticResourceExtension) { // Find the StaticResource ElementEnd record int elementDepth = 1; BamlRecord srStartRecord = bamlRecord; do { // Keep removing the baml records that belong to the current // static resource from the unshared list _unsharedContentCollection.RemoveAt(i); // Link records in the current static resource together bamlRecord.Next = _unsharedContentCollection[i]; bamlRecord = _unsharedContentCollection[i]; recordType = bamlRecord.RecordType; if (recordType == BamlRecordType.ElementStart || recordType == BamlRecordType.StaticResourceStart) { elementDepth++; } else if (recordType == BamlRecordType.ElementEnd || recordType == BamlRecordType.StaticResourceEnd) { elementDepth--; } } while (elementDepth > 0); _unsharedContentCollection.RemoveAt(i); bamlRecord.Next = null; // Ensure that we have a BamlReader and a StaticResourceValuesList EnsureBamlReaderAndStaticResourceValuesList(ref staticResourceValuesList, ref bamlReader); // Load the StaticResource records bamlReader.PreParsedRecordsStart = srStartRecord; bamlReader.PreParsedCurrentRecord = srStartRecord; bamlReader.Read(); StaticResourceExtension staticResource = (StaticResourceExtension)bamlReader.RootList[0]; bamlReader.RootList.Clear(); // Resolve the StaticResource value including lookup to the app and the theme DeferredResourceReference value = (DeferredResourceReference)bamlReader.PreviousBamlRecordReader.FindResourceInParentChain( staticResource.ResourceKey, true /*allowDeferredResourceReference*/, true /*mustReturnDeferredResourceReference*/); // Add the resolved value to the static resource list staticResourceValuesList.Add(value); // Replace the static resource records in this unshared // list with a StaticResourceIdRecord. BamlStaticResourceIdRecord bamlStaticResourceIdRecord = new BamlStaticResourceIdRecord(); bamlStaticResourceIdRecord.StaticResourceId = (short)(staticResourceValuesList.Count - 1); bamlStaticResourceIdRecord.Pin(); _unsharedContentCollection.Insert(i, bamlStaticResourceIdRecord); } } else if (recordType == BamlRecordType.PropertyWithExtension || recordType == BamlRecordType.OptimizedStaticResource) { IOptimizedMarkupExtension optimizedMarkupExtensionRecord = (IOptimizedMarkupExtension)bamlRecord; int positiveTypeId = optimizedMarkupExtensionRecord.ExtensionTypeId; if (positiveTypeId == (int)KnownElements.StaticResourceExtension) { // Remove the baml record with the StaticResource // from the unshared list _unsharedContentCollection.RemoveAt(i); bamlRecord.Next = null; // Ensure that we have a BamlReader and a StaticResourceValuesList EnsureBamlReaderAndStaticResourceValuesList(ref staticResourceValuesList, ref bamlReader); // Load the StaticResource StaticResourceExtension staticResource = (StaticResourceExtension)bamlReader.GetExtensionValue(optimizedMarkupExtensionRecord, null); // Resolve the StaticResource value including lookup to the app and the theme DeferredResourceReference value = (DeferredResourceReference)bamlReader.PreviousBamlRecordReader.FindResourceInParentChain( staticResource.ResourceKey, true /*allowDeferredResourceReference*/, true /*mustReturnDeferredResourceReference*/); // Add the resolved value to the static resource list staticResourceValuesList.Add(value); BamlStaticResourceIdRecord bamlStaticResourceIdRecord; if (recordType == BamlRecordType.PropertyWithExtension) { // Replace the PropertyWithExtensionRecord in this unshared // list with a PropertyWithStaticResourceIdRecord. BamlPropertyWithStaticResourceIdRecord bamlPropertyWithStaticResourceIdRecord = new BamlPropertyWithStaticResourceIdRecord(); bamlPropertyWithStaticResourceIdRecord.AttributeId = ((BamlPropertyWithExtensionRecord)bamlRecord).AttributeId; bamlStaticResourceIdRecord = bamlPropertyWithStaticResourceIdRecord; } else { // Replace the OptimizedStaticResourceRecord in this unshared // list with a StaticResourceIdRecord. bamlStaticResourceIdRecord = new BamlStaticResourceIdRecord(); } bamlStaticResourceIdRecord.StaticResourceId = (short)(staticResourceValuesList.Count - 1); bamlStaticResourceIdRecord.Pin(); _unsharedContentCollection.Insert(i, bamlStaticResourceIdRecord); } } } if (staticResourceValuesList != null) { // Restore the reader on the ParserContext _parserContext.BamlReader = bamlReader.PreviousBamlRecordReader; staticResourceValues = staticResourceValuesList.ToArray(); } } return staticResourceValues; } //+------------------------------------------------------------------------- // // EnsureBamlReaderAndStaticResourceValuesList // // Ensure that we have a BamlReader and a StaticResourceCaluesList to work with // //+------------------------------------------------------------------------- private void EnsureBamlReaderAndStaticResourceValuesList( ref ArrayList staticResourceValuesList, ref BamlRecordReader bamlReader) { if (staticResourceValuesList == null) { // This is the first StaticResource we found // within the template content section staticResourceValuesList = new ArrayList(); bamlReader = new BamlRecordReader(); bamlReader.SetPreviousBamlRecordReader(_parserContext.BamlReader); bamlReader.ParserContext = _parserContext; bamlReader.RootList = new ArrayList(1); } } //+-------------------------------------------------------------------------- // // ReadRecord // // Read a single record and process it. A record is added to the // _unsharedContentCollection, or the value is added to _sharedProperties. // //+------------------------------------------------------------------------- private void ReadRecord(BamlRecord bamlRecord) { // First keep the _optimizableElementState up-to-date. This is used to // tell us if the current element is an FE or FCE (if it is, // we can use the optimizations). CheckElementStartForOptimization( bamlRecord ); // Some record types should have been filtered out before the baml record // collection was created by the TemplateBamlRecordReader if (bamlRecord.RecordType == BamlRecordType.PIMapping || bamlRecord.RecordType == BamlRecordType.AssemblyInfo || bamlRecord.RecordType == BamlRecordType.TypeInfo || bamlRecord.RecordType == BamlRecordType.TypeSerializerInfo || bamlRecord.RecordType == BamlRecordType.AttributeInfo || bamlRecord.RecordType == BamlRecordType.StringInfo || bamlRecord.RecordType == BamlRecordType.DocumentStart || bamlRecord.RecordType == BamlRecordType.DocumentEnd) { // Enum.ToString(culture) is [Obsolete] #pragma warning disable 0618 ThrowException(SRID.TemplateInvalidBamlRecord, bamlRecord.RecordType.ToString(XamlReaderHelper.EnglishUSCulture)); #pragma warning restore 0618 } // Are we in the middle of a property value that we know can't // be shared? If so, add this record to the _unsharedContentCollection list. if (_currentState == ShareableRecordState.UnshareableSubtree) { AddBamlRecordToUnsharedContentCollection(bamlRecord); // Also, keep our tree-depth counters up-to-date. TrackComplexPropertyTreeDepth(bamlRecord); // If we reached the end of the property value that couldn't be shared, then reset // state (maybe the next property value will be shareable). if( CurrentIsOptimizableElement() ) { _currentState = ShareableRecordState.Unset; } } // Or are we in the middle of processing a set of records that is // *shareable*? else if (_currentState == ShareableRecordState.BuildingShareableTree) { // We are currently instantiating a possibly shared subtree, so pass // off the baml record to the record reader for processing and // instantiation. Note that the record reader will perform another // set of checks to determine if it needs to bail out of shareable // subtree processing and go back to being an unshareable subtree. ReadSharedRecord( bamlRecord ); } // If we fall to this else, we're not in a shareable tree or an unshareable tree; // we don't know what state to be in. We need to look at this record and figure // out where to put it (it may be the start of a shareable/unshareable tree). else { ReadPotentiallyShareableRecord( bamlRecord ); } } //+------------------------------------------------------------------------------------------------------ // // ReadSharedRecord // // This is called by ReadRecord, when we're processing baml records in the template content. It's // called when we're in a tree of records that will be a shared property. Note, though, that we // may stop sharing during this routine, if the record turns out to be un-shareable. // //+------------------------------------------------------------------------------------------------------ private void ReadSharedRecord( BamlRecord bamlRecord ) { // We are currently instantiating a possibly shared subtree, so pass // off the baml record to the record reader for processing and // instantiation. Note that the record reader will perform another // set of checks to determine if it needs to bail out of shareable // subtree processing and go back to being an unshareable subtree. // If that is the case, change status to UnshareableSubtree and copy // the portion of the subtree read so far into the unshareable list. BamlSubtreeState status = _optimizedTemplateContentHelper.ReadSubtreeRecord(bamlRecord); // Did the _optimizedTemplateContentHelper just decide that this sub-tree // isn't shareable after all? if (status == BamlSubtreeState.NotShareable) { // Yes. Reset our current state, copy any // records that we have already passed over into the _unsharedContentCollection // and continue on UnwindSharedRecords( bamlRecord ); _currentState = ShareableRecordState.Unset; //ShareableRecordState.UnshareableSubtree; } // Or, we're still in a shareable subtree, but maybe this // is the end of the subtree? else if (status == BamlSubtreeState.EndShareableSubtree) { // Yes, the subtree is shareable and we've reached the end of the tree. // Update the Dp value in the shared list. SharedDp sdp = _sharedProperties[_sharedProperties.Count-1]; sdp.Value = _optimizedTemplateContentHelper.RootList[0]; // Freezable freezableValue; System.Windows.Data.CollectionViewSource collectionViewSource; bool canShare = true; // This value is intended to be shared by all instances of the template. So freeze it, // call ME.ProvideValue on it, etc. StyleHelper.ProcessSharedPropertyValue( _parserContext, sdp, sdp.Dp, ref sdp.Value ); // We may not actually be able to share this value, though. In // that case, we'll have to release this instance. So figure out // that now. if (sdp.Value == null) { canShare = true; } else { Type valueType = sdp.Value.GetType(); // First, is this a shareable type? canShare = IsShareableType(valueType); // Second, some types are potentially shareable, but we have to inspect the instance to // know for sure. if (canShare) { if ((freezableValue = sdp.Value as Freezable) != null) { canShare = freezableValue.CanFreeze; } else if ((collectionViewSource = sdp.Value as System.Windows.Data.CollectionViewSource) != null) { canShare = collectionViewSource.IsShareableInTemplate(); } } // Finally, the only MEs we should have at this point are // binding and dynamic resources; we already called ProvideValue, // so any other ME isn't something we know about. if( canShare && valueType.IsAssignableFrom(typeof(MarkupExtension))) { if( !valueType.IsAssignableFrom(typeof(BindingBase)) && !valueType.IsAssignableFrom(typeof(TemplateBindingExtension)) && !valueType.IsAssignableFrom(typeof(DynamicResourceExtension)) ) { canShare = false; } } } // If it can't be shared, put the records into the unshared list. if( !canShare ) { UnwindSharedRecords( bamlRecord ); } else { // This can be shared, so we don't need to keep this BamlRecord in memory any more. while( _bamlRecordsSubtreeStart != null ) { BamlRecord nextRecord = _bamlRecordsSubtreeStart.Next; _bamlRecordsSubtreeStart.Unpin(); // Since we're not pinning this record any more, we're essentially // returning it to the cache, so we want to clear any Next reference. // But if it's still pinned (i.e., it's part of a nested template), // then we can't modify it. if( _bamlRecordsSubtreeStart.PinnedCount == 0 ) { _bamlRecordsSubtreeStart.Next = null; } if( _bamlRecordsSubtreeStart == bamlRecord ) _bamlRecordsSubtreeStart = null; else _bamlRecordsSubtreeStart = nextRecord; } } // Reset all the status trackers. _optimizedTemplateContentHelper.Reset(); _currentState = ShareableRecordState.Unset; } } //+--------------------------------------------------------------------- // // UnwindSharedRecords // // Remove the last property value from the _sharedProperty collection, // because, as it turns out, it's not shareable. Put all the Baml // records for that property into the list of baml records for // unshareable content (_unsharedContentCollection). // //+---------------------------------------------------------------------- private void UnwindSharedRecords( BamlRecord bamlRecord ) { Debug.Assert( _optimizableElementState.Count >= _positionOptimizableElementState ); // Update the _optimizableElementState back to where it was when we // started processing this property value that we thought // was going to be shared. while( _optimizableElementState.Count > _positionOptimizableElementState ) { _optimizableElementState.Pop(); } // Remove the value from the shared properties table _sharedProperties.RemoveAt(_sharedProperties.Count - 1); // Get the baml records for this property value and put them // into _unsharedContentCollection (the first baml records // for this value is pointed to be _bamlRecordsSubtreeStart). // (This code overlaps AddContentRecord, should be a better // way to share). while( _bamlRecordsSubtreeStart != null ) { BamlRecord bamlRecordT = _bamlRecordsSubtreeStart; CheckElementStartForOptimization( bamlRecordT ); CheckForName( bamlRecordT); _unsharedContentCollection.Add(bamlRecordT); if( _bamlRecordsSubtreeStart == bamlRecord ) _bamlRecordsSubtreeStart = null; else _bamlRecordsSubtreeStart = _bamlRecordsSubtreeStart.Next; } _optimizedTemplateContentHelper.Reset(); } //+------------------------------------------------------------------------------------------------- // // ReadPotentiallyShareableRecord // // This is called by ReadRecord, when we're processing baml records in the template content. It's // called when we don't know if this record should be shared or not. // //+------------------------------------------------------------------------------------------------- private void ReadPotentiallyShareableRecord( BamlRecord bamlRecord ) { DependencyProperty dp; object dpValue; // Check whether this record is shareable or not. If it is // shareable and is a DependencyProperty of some type, then // store it to be added to the template's shared value list. _currentState = LookForShareableRecord(bamlRecord, out dp, out dpValue); // Check if we need an x:Name associated with the current element or // not. This is dependent on the current state. Note that during this // call, we could get a new/replacement bamlRecord back. CheckForName( bamlRecord); // If this is an unshareable record, add it to the list. if (_currentState == ShareableRecordState.UnshareableRecord || _currentState == ShareableRecordState.UnshareableSubtree) { AddBamlRecordToUnsharedContentCollection(bamlRecord); } // Or, maybe we're starting a (potentially) shareable subtree else if (_currentState == ShareableRecordState.StartShareableTree) { // If we get to here we are starting a shared (or possibly unshared) // subtree. In this case remember where this subtree starts, in case // we need to unwind. OnStartShareableTree( bamlRecord ); // We're now in a shareable subtree mode _currentState = ShareableRecordState.BuildingShareableTree; // Remember the current dp. This may be removed if we later determine // this is not a shareable subtree. _sharedProperties.Add(new SharedDp(dp, null, CurrentElementName)); } else if (_currentState == ShareableRecordState.ShareableRecord) { // The call to GetShareableRecordState above actually figured out // the value for us. First, freeze it (if it's a Freezable), since // we don't handle changes to shared values. StyleHelper.ProcessSharedPropertyValue( _parserContext, _frameworkTemplate, dp, ref dpValue ); // Then store the property until the end of the start // tag, at which point we should have the x:Name needed to add // all the shared properties to the templates shared list. Shared // properties that don't have the element name set yet are indicated by // having a null name. if (String.IsNullOrEmpty(CurrentElementName)) _sharedProperties.Add(new SharedDp(dp, dpValue, null)); else _sharedProperties.Add(new SharedDp(dp, dpValue, CurrentElementName)); // Let this record be recycled by the BamlRecordManager's cache. bamlRecord.Next = null; bamlRecord.Unpin(); } } //+----------------------------------------------------------------------- // // OnStartShareableTree // // This remembers some state as we start processing baml records // for a potentially shareable property value; if it turns out not to // be shareable, we'll use the fields set here to unwind and put those // baml records into the unshareable collection. // //+------------------------------------------------------------------------ private void OnStartShareableTree( BamlRecord bamlRecord ) { // Remember the first baml records _bamlRecordsSubtreeStart = bamlRecord; _positionOptimizableElementState = _optimizableElementState.Count; } //+--------------------------------------------------------------------------- // // CheckElementStartForOptimization // // This is called by ReadRecord, and is used to keep the _optimizableElementState // up-to-date. This is used to tell us if the current element is an FE or FCE // (if it is, we can use the optimizations). // //+---------------------------------------------------------------------------- // These flags keep track of whether we're on an optimizable element, // i.e. one that will become a template child. It also tracks if we're // on or under a name scope. [Flags] private enum OptimizableElementState { Optimizable = 1, // An FE or FCE, and not in a nested name scope OnNestedNameScope = 2, // E.g. this element implements INameScope WhithinNestedNamescope = 4 // Has an INameScope as an ancestor } private void CheckElementStartForOptimization( BamlRecord bamlRecord ) { OptimizableElementState newState = 0; // Initialize newState if( _optimizableElementState.Count > 0 ) { OptimizableElementState previousState = _optimizableElementState.Peek(); // If we were at or within a namescope before, we're within one now. if( (previousState & (OptimizableElementState.WhithinNestedNamescope | OptimizableElementState.OnNestedNameScope)) != 0 ) { newState = OptimizableElementState.WhithinNestedNamescope; } } switch( bamlRecord.RecordType ) { case BamlRecordType.PropertyArrayStart: case BamlRecordType.PropertyIListStart: case BamlRecordType.PropertyIDictionaryStart: /* case BamlRecordType.PropertyComplexStart: case BamlRecordType.Text: */ // If we're in a property element, we're basically not // in an FE or FCE any more (we won't be doing any shared // value optimizations). _optimizableElementState.Push( newState ); break; case BamlRecordType.ElementStart: // If newState was set above, we don't need to update it. if( newState == 0) { BamlElementStartRecord bamlElementStartRecord = bamlRecord as BamlElementStartRecord; Type elementType = MapTable.GetTypeFromId( bamlElementStartRecord.TypeId ); // If this is an IComponentConnector, then we know that is a name scope. // If this is an IComponentConnector/DO, we assume it is a name scope. This is // slightly over-restrictive, but as close as we can get, while still allowing // for the common UserControl scenario. if( typeof(IComponentConnector).IsAssignableFrom(elementType) && typeof(DependencyObject).IsAssignableFrom(elementType) || typeof(INameScope).IsAssignableFrom(elementType) ) { newState = OptimizableElementState.OnNestedNameScope; } // If this is an FE or FCE, it is optimizable. Note that this means // that an element can be optimizable (a template child) at a namescope, but // not below one. if( KnownTypes.Types[(int)KnownElements.FrameworkElement].IsAssignableFrom( elementType) || KnownTypes.Types[(int)KnownElements.FrameworkContentElement].IsAssignableFrom( elementType ) ) { newState |= OptimizableElementState.Optimizable; } } _optimizableElementState.Push( newState ); break; case BamlRecordType.ElementEnd: case BamlRecordType.PropertyArrayEnd: case BamlRecordType.PropertyIListEnd: case BamlRecordType.PropertyIDictionaryEnd: // Pop the above stack. _optimizableElementState.Pop(); break; } } //+-------------------------------------------------------------------------------------- // // TrackComplexPropertyTreeDepth // // Determine where we are in a subtree within a complex property. This is // used to determine when to stop processing a shared or unshared subtree. // //+------------------------------------------------------------------------------------- private void TrackComplexPropertyTreeDepth(BamlRecord bamlRecord) { } //+-------------------------------------------------------------------------------------- // // CheckForName // // Look for a name property within the template content records. If we find // one, we know we can't share the record, and for FE/FCE elements we can // update the shared values type for this element. // //+------------------------------------------------------------------------------------- private void CheckForName( BamlRecord bamlRecord ) { switch (bamlRecord.RecordType) { case BamlRecordType.ElementStart: // If we were searching for a name on the last record, we can stop // searching now, we're not going to find one. if( _searchingForName ) { _searchingForName = false; // Update the shared values table to keep the generated name. UpdateSharedPropertyNames(); } { BamlNamedElementStartRecord bamlNamedElementStartRecord = bamlRecord as BamlNamedElementStartRecord; bool inFeOrFce = CurrentIsOptimizableElement(); // If this is an FE/FCE, name it (this temp name might get replaced later, // if the content actually has one). if( inFeOrFce ) { // Generate a name. We'll use an illegal name, because we're past name validation at this // point, and that guarantees that we won't have a conflict (it's invalid because it begins // with a numeric). string newName = (_generatedNameIndex++).ToString(CultureInfo.InvariantCulture) + "_T"; // Indicate that this is a temp name, and we're still searching for a real name _searchingForName = true; // Put this name on the stack and into the record. _nameStack.Push( newName ); bamlNamedElementStartRecord.RuntimeName = newName; // Also set a bit on the record indicating that this is a template child. // This is used during template application as a validation step to ensure // that child elements in a template do not implement a run-time name scope. bamlNamedElementStartRecord.IsTemplateChild = true; } else { // We're not in an FE/FCE, but keep the stack consistent. _nameStack.Push( String.Empty ); } } break; case BamlRecordType.ElementEnd: // If we were searching for a name on the last record, we can stop // searching now, we're not going to find one. if( _searchingForName ) { _searchingForName = false; UpdateSharedPropertyNames(); } // [....] the name stack. _nameStack.Pop(); break; case BamlRecordType.Property: case BamlRecordType.PropertyWithConverter: // See if we found the x:Name attribute BamlPropertyRecord propertyRecord = bamlRecord as BamlPropertyRecord; if (_searchingForName && MapTable.DoesAttributeMatch(propertyRecord.AttributeId, BamlAttributeUsage.RuntimeName)) { // Yes, this is the runtime name // Look in the unshareable content for the generated name, and replace it with the real name. for( int i = _unsharedContentCollection.Count - 1; i >= 0; --i ) { if (_unsharedContentCollection[i].RecordType == BamlRecordType.ElementStart) { BamlNamedElementStartRecord bamlNamedElementStartRecord = _unsharedContentCollection[i] as BamlNamedElementStartRecord; if( bamlNamedElementStartRecord.RuntimeName != null ) { bamlNamedElementStartRecord.RuntimeName = propertyRecord.Value; break; } } } // Correct the name stack _nameStack.Pop(); _nameStack.Push( propertyRecord.Value ); // Correct the naming in the shared values table _searchingForName = false; // This must be set before calling UpdateSharedPropertyNames UpdateSharedPropertyNames(); } break; case BamlRecordType.PropertyTypeReference: case BamlRecordType.PropertyStringReference: case BamlRecordType.PropertyCustom: case BamlRecordType.PropertyWithExtension: break; case BamlRecordType.PropertyComplexStart: case BamlRecordType.PropertyArrayStart: case BamlRecordType.PropertyIListStart: case BamlRecordType.PropertyIDictionaryStart: case BamlRecordType.Text: // If we were searching for a name on the last record, we can stop // searching now, we're not going to find one. if( _searchingForName ) { _searchingForName = false; UpdateSharedPropertyNames(); } break; case BamlRecordType.DefAttribute: BamlDefAttributeRecord defAttributeRecord = bamlRecord as BamlDefAttributeRecord; if (defAttributeRecord.NameId == BamlMapTable.NameStringId && _searchingForName) { // Look in the unshareable content for the generated name, and replace it with the real name. for( int i = _unsharedContentCollection.Count - 1; i >= 0; --i ) { if (_unsharedContentCollection[i].RecordType == BamlRecordType.ElementStart) { BamlNamedElementStartRecord bamlNamedElementStartRecord = _unsharedContentCollection[i] as BamlNamedElementStartRecord; if( bamlNamedElementStartRecord.RuntimeName != null ) { bamlNamedElementStartRecord.RuntimeName = defAttributeRecord.Name; bamlRecord = null; break; } } } // Correct the name stack _nameStack.Pop(); _nameStack.Push( defAttributeRecord.Name ); // Correct the shared property names table _searchingForName = false; // This must be set before calling UpdateSharedPropertyNames UpdateSharedPropertyNames(); } break; case BamlRecordType.RoutedEvent: case BamlRecordType.ClrEvent: // Should never reach here Debug.Assert( false, "Shouldn't see events at this point in the template code" ); break; default: break; } } //+----------------------------------------------------------------------------------------- // // UpdateSharedList // // Update the last items on the shared DependencyProperty list with the // name of the current element. // //+----------------------------------------------------------------------------------------- private void UpdateSharedPropertyNames() { if (_nameStack.Count > 0) { // Get the name of the current element string name = CurrentElementName; #if DEBUG int lastIndex = _frameworkTemplate._lastChildIndex; int childIndex = #endif // Generate an index for this name StyleHelper.CreateChildIndexFromChildName(CurrentElementName, _frameworkTemplate); #if DEBUG Debug.Assert( childIndex == lastIndex ); #endif // The tail of the _sharedProperties list has the properties for this element, // all with no name. Fill in the name now. for (int i = _sharedProperties.Count - 1; i >= 0; i--) { SharedDp sdp = _sharedProperties[i]; if (sdp.ElementName == null) { sdp.ElementName = name; } else { break; } } } } //+------------------------------------------------------------------ // // LookForShareableRecord // // This is called when we don't know whether or not a record can // be shared. E.g. it's not called when we know we're in a shareable // or un-shareable tree already. // // If the returned value is ShareableRecord, then dp and dpValue will // hold the value to be shared. // // Properties are shareable if they're a DP, on an FE, and the property // type is shareable (e.g. a Freezable that can't be frozen isn't // shareable). // //+----------------------------------------------------------------- private ShareableRecordState LookForShareableRecord( BamlRecord bamlRecord, out DependencyProperty dp, out object dpValue) { dp = null; dpValue = null; bool isFeOrFce = CurrentIsOptimizableElement(); // We'll assume that this isn't shareable. ShareableRecordState status = ShareableRecordState.UnshareableRecord; switch (bamlRecord.RecordType) { // Simple properties have 5 variations: // Property that contains just a string to be converted using a typeconverter // reflected for at runtime // PropertyWithConverter that contains a string and the typeid for the converter // PropertyTypeReference contains a typeid for a property that has a Type value // PropertyStringReference contains a string id for properties that are strings // PropertyCustom contains a custom binary representation of a property case BamlRecordType.Property: if (isFeOrFce) { BamlPropertyRecord bamlPropertyRecord = bamlRecord as BamlPropertyRecord; if (ParseDependencyProperty(bamlPropertyRecord.Value, bamlPropertyRecord.AttributeId, 0, out dp, out dpValue)) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } } break; case BamlRecordType.PropertyWithConverter: if( isFeOrFce ) { BamlPropertyWithConverterRecord bamlPropertyConverterRecord = bamlRecord as BamlPropertyWithConverterRecord; if (ParseDependencyProperty(bamlPropertyConverterRecord.Value, bamlPropertyConverterRecord.AttributeId, bamlPropertyConverterRecord.ConverterTypeId, out dp, out dpValue)) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } } break; case BamlRecordType.PropertyTypeReference: if( isFeOrFce ) { BamlPropertyTypeReferenceRecord bamlPropertyTypeRecord = bamlRecord as BamlPropertyTypeReferenceRecord; Type valueType = MapTable.GetTypeFromId(bamlPropertyTypeRecord.TypeId); dp = MapTable.GetDependencyProperty(bamlPropertyTypeRecord.AttributeId); if (dp != null) { dpValue = valueType; // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } } break; case BamlRecordType.PropertyStringReference: if( isFeOrFce ) { BamlPropertyStringReferenceRecord bamlPropertyStringRecord = bamlRecord as BamlPropertyStringReferenceRecord; string attribValue = _optimizedTemplateContentHelper.GetPropertyValueFromStringId(bamlPropertyStringRecord.StringId); if (ParseDependencyProperty(attribValue, bamlPropertyStringRecord.AttributeId, 0, out dp, out dpValue)) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } } break; case BamlRecordType.PropertyCustom: BamlPropertyCustomRecord bamlPropertyCustomRecord = bamlRecord as BamlPropertyCustomRecord; if (ReadCustomProperty(bamlPropertyCustomRecord, out dp, out dpValue) && isFeOrFce) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } break; case BamlRecordType.PropertyWithExtension: if (isFeOrFce) { BamlPropertyWithExtensionRecord bamlPropertyRecord = bamlRecord as BamlPropertyWithExtensionRecord; if (ReadPropertyWithExtension(bamlPropertyRecord, out dp, out dpValue)) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.ShareableRecord; } } break; // If this is a property element (complex property), we'll try to share it. If it's // some kind of collection, we don't support sharing for it; sharing for collection properties // gets confusing, because some people expect merge semantics rather than replacement semantics. case BamlRecordType.PropertyComplexStart: if (isFeOrFce) { BamlPropertyComplexStartRecord propertyComplexStartRecord = bamlRecord as BamlPropertyComplexStartRecord; dp = MapTable.GetDependencyProperty(propertyComplexStartRecord.AttributeId); if (dp != null) { // This is a shareable property that is a DP on an FE/FCE status = ShareableRecordState.StartShareableTree; } } break; } return status; } internal bool ReadPropertyWithExtension(BamlPropertyWithExtensionRecord bamlPropertyRecord, out DependencyProperty dp, out object propertyValue) { short attributeId = bamlPropertyRecord.AttributeId; dp = MapTable.GetDependencyProperty(attributeId); propertyValue = null; // Not shareable if not a DP if (dp == null) { return false; } propertyValue = _optimizedTemplateContentHelper.GetExtensionValue(bamlPropertyRecord, dp.Name); // MarkupExtensions are always shareable except for StaticResourceExtension. bool isShareable = (bamlPropertyRecord.ExtensionTypeId != (short)KnownElements.StaticResourceExtension); return isShareable; } internal bool ReadCustomProperty(BamlPropertyCustomRecord bamlPropertyCustomRecord, out DependencyProperty dp, out object propertyValue) { bool isShareable = false; if (bamlPropertyCustomRecord.SerializerTypeId == (short)KnownElements.DependencyPropertyConverter) { dp = null; propertyValue = _optimizedTemplateContentHelper.GetCustomDependencyPropertyValue(bamlPropertyCustomRecord); } else { string propName = null; Type propType = null; short attributeId = bamlPropertyCustomRecord.AttributeId; dp = MapTable.GetDependencyProperty(attributeId); if (dp == null) { propType = MapTable.GetCLRPropertyTypeAndNameFromId(attributeId, out propName); } else { propType = dp.PropertyType; propName = dp.Name; } // Read the custom property value into the record. propertyValue = _optimizedTemplateContentHelper.GetCustomValue(bamlPropertyCustomRecord, propType, propName); // Not shareable if not a DP if (dp != null) { isShareable = IsShareable(propertyValue, propType); } } return isShareable; } private bool IsShareable(object propValue, Type propType) { // Not shareable if if this is a Visual or ContentElement if (!IsShareableType(propType)) { return false; } // Not shareable if this is a Freezable that cannot be frozen. Freezable freezable = propValue as Freezable; if (freezable != null && !freezable.CanFreeze) { return false; } // Otherwise assume it can be shared. return true; } //+-------------------------------------------------------------------------------------- // // ParseDependencyPropeprty // // Process a single property value. Return true if it is a DependencyProperty // with a non-null value that is shareable. Return false if this property // is not shareable for some reason. // //+-------------------------------------------------------------------------------------- internal bool ParseDependencyProperty( string attribValue, short attributeId, short converterTypeId, out DependencyProperty dp, out object propertyValue) { Debug.Assert( CurrentIsOptimizableElement() ); // Figure out the DP from the attribute ID dp = MapTable.GetDependencyProperty(attributeId); propertyValue = null; // If we didn't find a DP, it's not shareable if (dp == null) { return false; } // Get the value for the DependencyProperty. propertyValue = XamlTypeMapper.ParseProperty(null, dp.PropertyType, dp.Name, dp, TypeConvertContext, _parserContext, attribValue, converterTypeId); return IsShareable(propertyValue, propertyValue.GetType()); } // IsShareableType // // See if a type is at least potentially shareabl across // applications of a template. // static internal bool IsShareableType( Type t ) { if( // We handle Freezables on an per-instance basis. KnownTypes.Types[(int)KnownElements.Freezable].IsAssignableFrom(t) || // Well-known immutable CLR types t == typeof(string) || t == typeof(Uri) || t == typeof(Type) || // We assume MEs are shareable; the host object is responsible // for ensuring immutability. The exception is static resource // references, for which we have special support in templates. (typeof(MarkupExtension).IsAssignableFrom(t) && !typeof(StaticResourceExtension).IsAssignableFrom(t)) || // Styles & Templates are mostly shareable typeof(Style).IsAssignableFrom(t) || typeof(FrameworkTemplate).IsAssignableFrom(t) || // CVS might be shareable, wait for the instance check typeof(CollectionViewSource).IsAssignableFrom(t) || // Value types are immutable by nature t.IsValueType ) { return true; } else { return false; } } // // ReleaseSharedProperties // // Null out the _sharedProperties. This is called during seal, // once we've pulled everything out of the shared properties that we need. // So free some heap space. // internal void ReleaseSharedProperties() { _sharedProperties = null; } #endregion Internals #region Helpers //+------------------------------------------------------------------------------------- // // OptimizedTemplateContent helper methods // //+-------------------------------------------------------------------------------------- // Throw an exception with one parameter private void ThrowException( string id, string parameter1) { string message = SR.Get(id, parameter1); //throw new XamlParseException(message); XamlParseException.ThrowException( _parserContext, _parserContext.LineNumber, _parserContext.LinePosition, message, null ); } // The BamlMapTable from the ParserContext internal BamlMapTable MapTable { get { // We keep this alive even after the template content // has been optimized, because we need it during // instantiation. if (_mapTable == null) { _mapTable = _parserContext.MapTable; } return _mapTable; } } // See if this optimized template is empty internal bool IsEmpty { get { return _unsharedContent == null && ( _sharedProperties == null || _sharedProperties.Count == 0 ); } } // The XamlTypeMapper from the ParserContext private XamlTypeMapper XamlTypeMapper { get { return _parserContext.XamlTypeMapper; } } // Wrapper to tell if the current element is an FE or an FCE // (using the _optimizableElementState) private bool CurrentIsOptimizableElement() { return _optimizableElementState.Count != 0 && (_optimizableElementState.Peek() & OptimizableElementState.Optimizable) != 0; } // Generate a TypeConvertContext private TypeConvertContext TypeConvertContext { get { if (null == _typeConvertContext) { _typeConvertContext = new TypeConvertContext(_parserContext); } return _typeConvertContext; } } // Get the name of the current element private string CurrentElementName { get { // No names exist yet? if (_nameStack.Count == 0) return null; // Are we still searching for a name? // (If so, ignore the generated name on the stack) else if( _searchingForName ) return null; // Otherwise, return the real name from the stack. else return _nameStack.Peek() as String; } } // The Baml records that have to be re-instantiated every time. internal BamlRecord UnsharedContent { get { return _unsharedContent; } } // The property values that can be shared between instantiations // of the template. internal FrugalObjectListSharedProperties { get { return _sharedProperties; } } // Type of the template content's root element (used for validation). internal Type RootType { get { return _rootType; } } #endregion Helpers #region Data private ParserContext _parserContext; private Stack _optimizableElementState; private BamlMapTable _mapTable; private OptimizedTemplateContentHelper _optimizedTemplateContentHelper; private FrameworkTemplate _frameworkTemplate; private ShareableRecordState _currentState; private bool _searchingForName; private BamlRecord _unsharedContent; private FrugalObjectList _sharedProperties; private Stack _nameStack; private Collection _unsharedContentCollection; private TypeConvertContext _typeConvertContext; private BamlRecord _bamlRecordsSubtreeStart; private Type _rootType; // Type of the content's root element private int _positionOptimizableElementState; // Used by StartShreableTree private int _generatedNameIndex = 0; #endregion Data } } // 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
- PointAnimationBase.cs
- SynchronizedDisposablePool.cs
- Point3DCollectionValueSerializer.cs
- ExclusiveHandleList.cs
- CacheForPrimitiveTypes.cs
- UnicodeEncoding.cs
- ImageMetadata.cs
- ZipIOExtraFieldPaddingElement.cs
- RawStylusInputReport.cs
- MetadataException.cs
- XmlObjectSerializerWriteContextComplex.cs
- ColorAnimationBase.cs
- NamespaceInfo.cs
- BinaryObjectInfo.cs
- GenerateTemporaryTargetAssembly.cs
- ToolStripProgressBar.cs
- VirtualDirectoryMapping.cs
- QueryTask.cs
- CodeTypeParameter.cs
- SafePointer.cs
- EventLogPermissionAttribute.cs
- StringBlob.cs
- XmlProcessingInstruction.cs
- XmlCountingReader.cs
- KeySplineConverter.cs
- HierarchicalDataBoundControl.cs
- TransformCryptoHandle.cs
- GradientStopCollection.cs
- ComponentRenameEvent.cs
- ButtonFieldBase.cs
- DispatcherProcessingDisabled.cs
- SettingsSavedEventArgs.cs
- StreamUpdate.cs
- SizeConverter.cs
- SQLDecimal.cs
- TreeNodeConverter.cs
- PageAsyncTaskManager.cs
- CLRBindingWorker.cs
- DependencyPropertyKind.cs
- AutomationPatternInfo.cs
- StylusEventArgs.cs
- DataFieldConverter.cs
- CodeBinaryOperatorExpression.cs
- CommandCollectionEditor.cs
- SimplePropertyEntry.cs
- ReadOnlyMetadataCollection.cs
- MultiSelector.cs
- Speller.cs
- ResourceCategoryAttribute.cs
- CommaDelimitedStringAttributeCollectionConverter.cs
- BindableTemplateBuilder.cs
- CodeSubDirectory.cs
- Utility.cs
- CodePageEncoding.cs
- SaveFileDialog.cs
- TimeoutHelper.cs
- FileSystemWatcher.cs
- SafePEFileHandle.cs
- XmlSubtreeReader.cs
- SqlGatherConsumedAliases.cs
- BlurBitmapEffect.cs
- PassportAuthenticationEventArgs.cs
- FrameworkRichTextComposition.cs
- NetPeerTcpBindingElement.cs
- HttpWebRequest.cs
- IsolationInterop.cs
- PreviewPrintController.cs
- MimeMapping.cs
- ActivityDesignerAccessibleObject.cs
- ApplicationBuildProvider.cs
- CapabilitiesUse.cs
- FilteredSchemaElementLookUpTable.cs
- DataGridCellsPresenter.cs
- SessionStateSection.cs
- ToolBarButtonDesigner.cs
- cache.cs
- StreamResourceInfo.cs
- XmlSchemaType.cs
- InstanceDescriptor.cs
- BitmapEffectDrawingContextWalker.cs
- DropShadowEffect.cs
- MimeParameterWriter.cs
- AssemblyCache.cs
- FolderLevelBuildProviderCollection.cs
- ValidationPropertyAttribute.cs
- Storyboard.cs
- AdornerHitTestResult.cs
- Misc.cs
- BlockUIContainer.cs
- NativeMethodsCLR.cs
- InvalidDataException.cs
- InsufficientExecutionStackException.cs
- EncodingDataItem.cs
- PrintEvent.cs
- RootProjectionNode.cs
- OdbcParameterCollection.cs
- NetSectionGroup.cs
- DoubleUtil.cs
- RenderCapability.cs
- CreateBookmarkScope.cs