| File: System\Windows\Forms\Rendering\DrawingEventArgs.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; namespace System.Windows.Forms; /// <summary> /// Struct that helps ensure we build our <see cref="EventArgs"/> that wrap <see cref="DrawingEventArgs"/> /// the same way. /// </summary> /// <remarks> /// <para> /// We should consider making this a base class for the event args that use this rather than a nested struct. /// That would make things a little more robust, but would require API review as the class itself would have to /// be public. The internal functionality can obviously still be internal. /// </para> /// </remarks> internal partial class DrawingEventArgs { private Graphics? _graphics; /// <summary> /// DC (Display context) for obtaining the graphics object. Used to delay getting the graphics object until /// absolutely necessary (for perf reasons) /// </summary> private readonly HDC _hdc; private HPALETTE _oldPalette; public DrawingEventArgs( Graphics graphics, Rectangle clipRect, DrawingEventFlags flags) { _graphics = graphics.OrThrowIfNull(); _hdc = default; _oldPalette = default; CheckGraphicsForState(graphics, flags); ClipRectangle = clipRect; Flags = flags; } /// <summary> /// Internal version of constructor for performance. We try to avoid getting the graphics object until needed. /// </summary> public DrawingEventArgs( HDC dc, Rectangle clipRect, DrawingEventFlags flags) { ArgumentValidation.ThrowIfNull(dc); #if DEBUG OBJ_TYPE type = (OBJ_TYPE)PInvokeCore.GetObjectType(dc); Debug.Assert(type is OBJ_TYPE.OBJ_DC or OBJ_TYPE.OBJ_ENHMETADC or OBJ_TYPE.OBJ_MEMDC or OBJ_TYPE.OBJ_METADC); #endif _hdc = dc; _graphics = null; _oldPalette = default; Flags = flags; ClipRectangle = clipRect; } internal DrawingEventFlags Flags { get; private set; } internal Rectangle ClipRectangle { get; } internal bool IsStateClean => !Flags.HasFlag(DrawingEventFlags.GraphicsStateUnclean); /// <summary> /// Gets the HDC this event is connected to. If there is no associated HDC, or the GDI+ Graphics object has /// been externally accessed (where it may have gotten a transform or clip) a null handle is returned. /// </summary> internal HDC HDC => IsStateClean ? default : _hdc; /// <summary> /// Gets the <see cref="Graphics"/> object used to paint. /// </summary> internal Graphics Graphics { get { // If we're giving this out on the public API expect it to get a clip or transform applied. Flags |= DrawingEventFlags.GraphicsStateUnclean; return GetOrCreateGraphicsInternal(); } } /// <summary> /// For internal use to improve performance. DO NOT use this method if you modify the Graphics Clip or Transform. /// </summary> internal Graphics GetOrCreateGraphicsInternal(Action<Graphics>? creationAction = null) { if (_graphics is null) { Debug.Assert(!_hdc.IsNull); // We need to manually unset the palette here so this scope shouldn't be disposed var paletteScope = _hdc.HalftonePalette( forceBackground: false, realizePalette: false); GC.SuppressFinalize(paletteScope); _oldPalette = paletteScope.HPALETTE; _graphics = Graphics.FromHdcInternal((IntPtr)_hdc); _graphics.PageUnit = GraphicsUnit.Pixel; creationAction?.Invoke(_graphics); CheckGraphicsForState(_graphics, Flags); } return _graphics; } internal HDC GetHDC() => _hdc; internal Graphics? GetGraphics(bool create) { CheckGraphicsForState(_graphics, Flags); return create ? GetOrCreateGraphicsInternal() : _graphics; } internal void Dispose(bool disposing) { if (disposing) { // Only dispose the graphics object if we created it via the HDC. if (_graphics is not null && !_hdc.IsNull) { _graphics.Dispose(); } } if (!_oldPalette.IsNull && !_hdc.IsNull) { PInvokeCore.SelectPalette(_hdc, _oldPalette, bForceBkgd: false); _oldPalette = default; } } [Conditional("DEBUG")] internal static void CheckGraphicsForState(Graphics? graphics, DrawingEventFlags flags) { if (graphics is null || !flags.HasFlag(DrawingEventFlags.CheckState) || flags.HasFlag(DrawingEventFlags.GraphicsStateUnclean)) { return; } // Check to see if we've actually corrupted the state graphics.GetContextInfo(out PointF offset, out Region? clip); using (clip) { bool isInfinite = clip?.IsInfinite(graphics) ?? true; Debug.Assert(offset.IsEmpty, "transform has been modified"); Debug.Assert(isInfinite, "clipping as been applied"); } } }