| File: System\Windows\Media\DrawingGroup.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Windows.Markup; using System.Windows.Media.Animation; namespace System.Windows.Media { /// <summary> /// DrawingGroup represents a collection of Drawing objects, and /// can apply group-operations such as clip and opacity to it's /// collections. /// </summary> [ContentProperty("Children")] public sealed partial class DrawingGroup : Drawing { #region Constructors /// <summary> /// Default DrawingGroup constructor. /// Constructs an object with all properties set to their default values. /// </summary> public DrawingGroup() { } #endregion Constructors #region Public methods /// <summary> /// Opens the DrawingGroup for re-populating it's children, clearing any existing /// children. /// </summary> /// <returns> /// Returns DrawingContext to populate the DrawingGroup's children. /// </returns> public DrawingContext Open() { VerifyOpen(); _openedForAppend = false; return new DrawingGroupDrawingContext(this); } /// <summary> /// Opens the DrawingGroup for populating it's children, appending to /// any existing children in the collection. /// </summary> /// <returns> /// Returns DrawingContext to populate the DrawingGroup's children. /// </returns> public DrawingContext Append() { VerifyOpen(); _openedForAppend = true; return new DrawingGroupDrawingContext(this); } #endregion Public methods #region Internal methods /// <summary> /// Called by a DrawingContext returned from Open or Append when the content /// created by it needs to be committed (because DrawingContext.Close/Dispose /// was called) /// </summary> /// <param name="rootDrawingGroupChildren"> /// Collection containing the Drawing elements created by a DrawingContext /// returned from Open or Append. /// </param> internal void Close(DrawingCollection rootDrawingGroupChildren) { WritePreamble(); Debug.Assert(_open); Debug.Assert(rootDrawingGroupChildren != null); if (!_openedForAppend) { // Clear out the previous contents by replacing the current collection with // the new collection. // // When more than one element exists in rootDrawingGroupChildren, the // DrawingContext had to create this new collection anyways. To behave // consistently between the one-element and many-element cases, // we always set Children to a new DrawingCollection instance during Close(). // // Doing this also avoids having to protect against exceptions being thrown // from user-code, which could be executed if a Changed event was fired when // we tried to add elements to a pre-existing collection. // // The collection created by the DrawingContext will no longer be // used after the DrawingContext is closed, so we can take ownership // of the reference here to avoid any more unneccesary copies. Children = rootDrawingGroupChildren; } else { // // // Append the collection to the current Children collection // // DrawingCollection children = Children; // // Ensure that we can Append to the Children collection // if (children == null) { throw new InvalidOperationException(SR.DrawingGroup_CannotAppendToNullCollection); } if (children.IsFrozen) { throw new InvalidOperationException(SR.DrawingGroup_CannotAppendToFrozenCollection); } // Append the new collection to our current Children. // // TransactionalAppend rolls-back the Append operation in the event // an exception is thrown from the Changed event. children.TransactionalAppend(rootDrawingGroupChildren); } // This DrawingGroup is no longer open _open = false; } /// <summary> /// Calls methods on the DrawingContext that are equivalent to the /// Drawing with the Drawing's current value. /// </summary> internal override void WalkCurrentValue(DrawingContextWalker ctx) { int popCount = 0; // We avoid unneccessary ShouldStopWalking checks based on assumptions // about when ShouldStopWalking is set. Guard that assumption with an // assertion. // // ShouldStopWalking is currently only set during a hit-test walk after // an object has been hit. Because a DrawingGroup can't be hit until after // the first Drawing is tested, this method doesn't check ShouldStopWalking // until after the first child. // // We don't need to add this check to other Drawing subclasses for // the same reason -- if the Drawing being tested isn't a DrawingGroup, // they are always the 'first child'. // // If this assumption is ever broken then the ShouldStopWalking // check should be done on the first child -- including in the // WalkCurrentValue method of other Drawing subclasses. Debug.Assert(!ctx.ShouldStopWalking); // // Draw the transform property // // Avoid calling PushTransform if the base value is set to the default and // no animations have been set on the property. if (!IsBaseValueDefault(DrawingGroup.TransformProperty) || (null != AnimationStorage.GetStorage(this, DrawingGroup.TransformProperty))) { ctx.PushTransform(Transform); popCount++; } // // Draw the clip property // // Avoid calling PushClip if the base value is set to the default and // no animations have been set on the property. if (!IsBaseValueDefault(DrawingGroup.ClipGeometryProperty) || (null != AnimationStorage.GetStorage(this, DrawingGroup.ClipGeometryProperty))) { ctx.PushClip(ClipGeometry); popCount++; } // // Draw the opacity property // // Avoid calling PushOpacity if the base value is set to the default and // no animations have been set on the property. if (!IsBaseValueDefault(DrawingGroup.OpacityProperty) || (null != AnimationStorage.GetStorage(this, DrawingGroup.OpacityProperty))) { // Push the current value of the opacity property, which // is what Opacity returns. ctx.PushOpacity(Opacity); popCount++; } // Draw the opacity mask property // if (OpacityMask != null) { ctx.PushOpacityMask(OpacityMask); popCount++; } // // Draw the effect property // // Push the current value of the effect property, which // is what BitmapEffect returns. if (BitmapEffect != null) { // Disable warning about obsolete method. This code must remain active // until we can remove the public BitmapEffect APIs. #pragma warning disable 0618 ctx.PushEffect(BitmapEffect, BitmapEffectInput); #pragma warning restore 0618 popCount++; } // // Draw the Children collection // // Get the current value of the children collection DrawingCollection collection = Children; // Call Walk on each child if (collection != null) { for (int i = 0; i < collection.Count; i++) { Drawing drawing = collection.Internal_GetItem(i); if (drawing != null) { drawing.WalkCurrentValue(ctx); // Don't visit the remaining children if the previous // child caused us to stop walking. if (ctx.ShouldStopWalking) { break; } } } } // // Call Pop() for every Push // // Avoid placing this logic in a finally block because if an exception is // thrown, the Walk is simply aborted. There is no requirement to Walk // through Pop instructions when an exception is thrown. // for (int i = 0; i < popCount; i++) { ctx.Pop(); } } #endregion Internal methods #region Private Methods /// <summary> /// Called by both Open() and Append(), this method verifies the /// DrawingGroup isn't already open, and set's the open flag. /// </summary> private void VerifyOpen() { WritePreamble(); // Throw an exception if we are already opened if (_open) { throw new InvalidOperationException(SR.DrawingGroup_AlreadyOpen); } _open = true; } #endregion Private Methods #region Private fields private bool _openedForAppend; private bool _open; #endregion Private fields } }