Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Framework / System / Windows / Markup / OptimizedTemplateContent.cs / 2 / 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(System.Windows.Markup.TypeConverterHelper.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 FrugalObjectList SharedProperties
{
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(System.Windows.Markup.TypeConverterHelper.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 FrugalObjectList SharedProperties
{
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
- AttachedAnnotation.cs
- Pair.cs
- Exceptions.cs
- AssemblyUtil.cs
- TableLayoutStyle.cs
- Mouse.cs
- BrowserCapabilitiesCompiler.cs
- SerialErrors.cs
- ProcessModule.cs
- CodePropertyReferenceExpression.cs
- CompilerInfo.cs
- CompensatableTransactionScopeActivity.cs
- PropertyChangeTracker.cs
- ReflectionHelper.cs
- DurableInstance.cs
- SessionEndingCancelEventArgs.cs
- CancellationHandler.cs
- PanelStyle.cs
- BevelBitmapEffect.cs
- DataGridDesigner.cs
- NetSectionGroup.cs
- SecurityKeyEntropyMode.cs
- TemplateBindingExpression.cs
- exports.cs
- SimpleType.cs
- AtlasWeb.Designer.cs
- DesignerWithHeader.cs
- FormsAuthenticationUser.cs
- AdornedElementPlaceholder.cs
- AutoCompleteStringCollection.cs
- SchemaLookupTable.cs
- DoubleUtil.cs
- VariableQuery.cs
- CalendarDay.cs
- MemberHolder.cs
- PresentationSource.cs
- SafePointer.cs
- BinaryObjectReader.cs
- BaseCollection.cs
- RangeValidator.cs
- RectangleGeometry.cs
- InlinedAggregationOperatorEnumerator.cs
- WebRequestModulesSection.cs
- Command.cs
- TypeDescriptor.cs
- mediaeventargs.cs
- _SSPISessionCache.cs
- CngKeyCreationParameters.cs
- UIntPtr.cs
- ScopedMessagePartSpecification.cs
- WorkflowServiceNamespace.cs
- GridViewRowCollection.cs
- SqlCacheDependencySection.cs
- ResourceCategoryAttribute.cs
- XPathDocumentBuilder.cs
- CheckBoxList.cs
- DependencyProperty.cs
- ConfigXmlComment.cs
- SemaphoreSecurity.cs
- TreeNodeSelectionProcessor.cs
- CallbackValidator.cs
- DataGridViewLinkCell.cs
- SqlUDTStorage.cs
- Int16AnimationBase.cs
- mansign.cs
- SymbolType.cs
- AssociationEndMember.cs
- FormattedTextSymbols.cs
- IntSecurity.cs
- Model3DCollection.cs
- GlyphCache.cs
- MobileErrorInfo.cs
- SymmetricSecurityProtocolFactory.cs
- Win32.cs
- PointConverter.cs
- ThemeDirectoryCompiler.cs
- TextProperties.cs
- DetailsViewRow.cs
- XPathDocumentNavigator.cs
- BindValidationContext.cs
- UndoUnit.cs
- XPathAncestorIterator.cs
- PointValueSerializer.cs
- SystemInfo.cs
- CngUIPolicy.cs
- DataServiceHost.cs
- Options.cs
- DataSourceControlBuilder.cs
- PointUtil.cs
- TransformGroup.cs
- StreamHelper.cs
- _ProxyChain.cs
- HtmlInputFile.cs
- PeerNearMe.cs
- XmlDocumentSerializer.cs
- DesignColumn.cs
- XmlSchemaGroupRef.cs
- DataServiceConfiguration.cs
- SafeTimerHandle.cs
- CorrelationResolver.cs