File: System\Windows\Forms\Controls\DataGridView\DataGridViewLinkCell.cs
Web Access
Project: src\src\System.Windows.Forms\src\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.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
 
namespace System.Windows.Forms;
 
public partial class DataGridViewLinkCell : DataGridViewCell
{
    private const DataGridViewContentAlignment AnyLeft = DataGridViewContentAlignment.TopLeft | DataGridViewContentAlignment.MiddleLeft | DataGridViewContentAlignment.BottomLeft;
    private const DataGridViewContentAlignment AnyRight = DataGridViewContentAlignment.TopRight | DataGridViewContentAlignment.MiddleRight | DataGridViewContentAlignment.BottomRight;
    private const DataGridViewContentAlignment AnyBottom = DataGridViewContentAlignment.BottomRight | DataGridViewContentAlignment.BottomCenter | DataGridViewContentAlignment.BottomLeft;
 
    private static readonly Type s_defaultFormattedValueType = typeof(string);
    private static readonly Type s_defaultValueType = typeof(object);
    private static readonly Type s_cellType = typeof(DataGridViewLinkCell);
 
    private static readonly int s_propLinkCellActiveLinkColor = PropertyStore.CreateKey();
    private static readonly int s_propLinkCellLinkBehavior = PropertyStore.CreateKey();
    private static readonly int s_propLinkCellLinkColor = PropertyStore.CreateKey();
    private static readonly int s_propLinkCellLinkState = PropertyStore.CreateKey();
    private static readonly int s_propLinkCellTrackVisitedState = PropertyStore.CreateKey();
    private static readonly int s_propLinkCellUseColumnTextForLinkValue = PropertyStore.CreateKey();
    private static readonly int s_propLinkCellVisitedLinkColor = PropertyStore.CreateKey();
 
    private const byte HorizontalTextMarginLeft = 1;
    private const byte HorizontalTextMarginRight = 2;
    private const byte VerticalTextMarginTop = 1;
    private const byte VerticalTextMarginBottom = 1;
 
    // we cache LinkVisited because it will be set multiple times
    private bool _linkVisited;
    private bool _linkVisitedSet;
 
    private static Cursor? s_dataGridViewCursor;
 
    public DataGridViewLinkCell()
    {
    }
 
    public Color ActiveLinkColor
    {
        get
        {
            if (Properties.TryGetObject(s_propLinkCellActiveLinkColor, out Color color))
            {
                return color;
            }
            else if (SystemInformation.HighContrast)
            {
                return HighContrastLinkColor;
            }
            else
            {
                // return the default IE Color if cell is not not selected
                return Selected ? SystemColors.HighlightText : LinkUtilities.IEActiveLinkColor;
            }
        }
        set
        {
            if (!value.Equals(ActiveLinkColor))
            {
                Properties.SetObject(s_propLinkCellActiveLinkColor, value);
                if (DataGridView is not null)
                {
                    if (RowIndex != -1)
                    {
                        DataGridView.InvalidateCell(this);
                    }
                    else
                    {
                        DataGridView.InvalidateColumnInternal(ColumnIndex);
                    }
                }
            }
        }
    }
 
    internal Color ActiveLinkColorInternal
    {
        set
        {
            if (!value.Equals(ActiveLinkColor))
            {
                Properties.SetObject(s_propLinkCellActiveLinkColor, value);
            }
        }
    }
 
    private bool ShouldSerializeActiveLinkColor()
    {
        if (SystemInformation.HighContrast)
        {
            return !ActiveLinkColor.Equals(SystemColors.HotTrack);
        }
 
        return !ActiveLinkColor.Equals(LinkUtilities.IEActiveLinkColor);
    }
 
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.Interfaces)]
    public override Type? EditType
    {
        get
        {
            // links can't switch to edit mode
            return null;
        }
    }
 
    public override Type? FormattedValueType => s_defaultFormattedValueType;
 
    [DefaultValue(LinkBehavior.SystemDefault)]
    public LinkBehavior LinkBehavior
    {
        get => Properties.GetValueOrDefault(s_propLinkCellLinkBehavior, LinkBehavior.SystemDefault);
        set
        {
            // Sequential enum. Valid values are 0x0 to 0x3
            SourceGenerated.EnumValidator.Validate(value);
            if (value != LinkBehavior)
            {
                Properties.AddValue(s_propLinkCellLinkBehavior, value);
                if (DataGridView is not null)
                {
                    if (RowIndex != -1)
                    {
                        DataGridView.InvalidateCell(this);
                    }
                    else
                    {
                        DataGridView.InvalidateColumnInternal(ColumnIndex);
                    }
                }
            }
        }
    }
 
    internal LinkBehavior LinkBehaviorInternal
    {
        set
        {
            Debug.Assert(value is >= LinkBehavior.SystemDefault and <= LinkBehavior.NeverUnderline);
            if (value != LinkBehavior)
            {
                Properties.AddValue(s_propLinkCellLinkBehavior, value);
            }
        }
    }
 
    public Color LinkColor
    {
        get
        {
            if (Properties.TryGetObject(s_propLinkCellLinkColor, out Color color))
            {
                return color;
            }
            else if (SystemInformation.HighContrast)
            {
                return HighContrastLinkColor;
            }
            else
            {
                // return the default IE Color when cell is not selected
                return Selected ? SystemColors.HighlightText : LinkUtilities.IELinkColor;
            }
        }
        set
        {
            if (!value.Equals(LinkColor))
            {
                Properties.SetObject(s_propLinkCellLinkColor, value);
                if (DataGridView is not null)
                {
                    if (RowIndex != -1)
                    {
                        DataGridView.InvalidateCell(this);
                    }
                    else
                    {
                        DataGridView.InvalidateColumnInternal(ColumnIndex);
                    }
                }
            }
        }
    }
 
    internal Color LinkColorInternal
    {
        set
        {
            if (!value.Equals(LinkColor))
            {
                Properties.SetObject(s_propLinkCellLinkColor, value);
            }
        }
    }
 
    private bool ShouldSerializeLinkColor()
    {
        if (SystemInformation.HighContrast)
        {
            return !LinkColor.Equals(SystemColors.HotTrack);
        }
 
        return !LinkColor.Equals(LinkUtilities.IELinkColor);
    }
 
    private LinkState LinkState
    {
        get => Properties.GetValueOrDefault(s_propLinkCellLinkState, LinkState.Normal);
        set
        {
            if (LinkState != value)
            {
                Properties.AddValue(s_propLinkCellLinkState, value);
            }
        }
    }
 
    public bool LinkVisited
    {
        get
        {
            if (_linkVisitedSet)
            {
                return _linkVisited;
            }
 
            // the default is false
            return false;
        }
        set
        {
            _linkVisitedSet = true;
            if (value != LinkVisited)
            {
                _linkVisited = value;
                if (DataGridView is not null)
                {
                    if (RowIndex != -1)
                    {
                        DataGridView.InvalidateCell(this);
                    }
                    else
                    {
                        DataGridView.InvalidateColumnInternal(ColumnIndex);
                    }
                }
            }
        }
    }
 
    private bool ShouldSerializeLinkVisited() => _linkVisitedSet = true;
 
    [DefaultValue(true)]
    public bool TrackVisitedState
    {
        get => Properties.GetValueOrDefault(s_propLinkCellTrackVisitedState, true);
        set
        {
            if (value != TrackVisitedState)
            {
                Properties.AddValue(s_propLinkCellTrackVisitedState, value);
                if (DataGridView is not null)
                {
                    if (RowIndex != -1)
                    {
                        DataGridView.InvalidateCell(this);
                    }
                    else
                    {
                        DataGridView.InvalidateColumnInternal(ColumnIndex);
                    }
                }
            }
        }
    }
 
    internal bool TrackVisitedStateInternal
    {
        set
        {
            if (value != TrackVisitedState)
            {
                Properties.AddValue(s_propLinkCellTrackVisitedState, value);
            }
        }
    }
 
    [DefaultValue(false)]
    public bool UseColumnTextForLinkValue
    {
        get => Properties.GetValueOrDefault<bool>(s_propLinkCellUseColumnTextForLinkValue);
        set
        {
            if (value != UseColumnTextForLinkValue)
            {
                Properties.AddValue(s_propLinkCellUseColumnTextForLinkValue, value);
                OnCommonChange();
            }
        }
    }
 
    internal bool UseColumnTextForLinkValueInternal
    {
        set
        {
            // Caller is responsible for invalidation
            if (value != UseColumnTextForLinkValue)
            {
                Properties.AddValue(s_propLinkCellUseColumnTextForLinkValue, value);
            }
        }
    }
 
    public Color VisitedLinkColor
    {
        get
        {
            if (Properties.TryGetObject(s_propLinkCellVisitedLinkColor, out Color color))
            {
                return color;
            }
            else if (SystemInformation.HighContrast)
            {
                return Selected ? SystemColors.HighlightText : LinkUtilities.GetVisitedLinkColor();
            }
            else
            {
                // return the default IE Color if cell is not not selected
                return Selected ? SystemColors.HighlightText : LinkUtilities.IEVisitedLinkColor;
            }
        }
        set
        {
            if (!value.Equals(VisitedLinkColor))
            {
                Properties.SetObject(s_propLinkCellVisitedLinkColor, value);
                if (DataGridView is not null)
                {
                    if (RowIndex != -1)
                    {
                        DataGridView.InvalidateCell(this);
                    }
                    else
                    {
                        DataGridView.InvalidateColumnInternal(ColumnIndex);
                    }
                }
            }
        }
    }
 
    internal Color VisitedLinkColorInternal
    {
        set
        {
            if (!value.Equals(VisitedLinkColor))
            {
                Properties.SetObject(s_propLinkCellVisitedLinkColor, value);
            }
        }
    }
 
    private bool ShouldSerializeVisitedLinkColor()
    {
        if (SystemInformation.HighContrast)
        {
            return !VisitedLinkColor.Equals(SystemColors.HotTrack);
        }
 
        return !VisitedLinkColor.Equals(LinkUtilities.IEVisitedLinkColor);
    }
 
    private Color HighContrastLinkColor
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get
        {
            // Selected cells have SystemColors.Highlight as a background.
            // SystemColors.HighlightText is supposed to be in contrast with SystemColors.Highlight.
            return Selected ? SystemColors.HighlightText : SystemColors.HotTrack;
        }
    }
 
    public override Type ValueType
    {
        get
        {
            Type? valueType = base.ValueType;
            if (valueType is not null)
            {
                return valueType;
            }
 
            return s_defaultValueType;
        }
    }
 
    public override object Clone()
    {
        DataGridViewLinkCell dataGridViewCell;
        Type thisType = GetType();
 
        if (thisType == s_cellType) // performance improvement
        {
            dataGridViewCell = new DataGridViewLinkCell();
        }
        else
        {
            dataGridViewCell = (DataGridViewLinkCell)Activator.CreateInstance(thisType)!;
        }
 
        CloneInternal(dataGridViewCell);
 
        if (Properties.ContainsKey(s_propLinkCellActiveLinkColor))
        {
            dataGridViewCell.ActiveLinkColorInternal = ActiveLinkColor;
        }
 
        if (Properties.ContainsKey(s_propLinkCellUseColumnTextForLinkValue))
        {
            dataGridViewCell.UseColumnTextForLinkValueInternal = UseColumnTextForLinkValue;
        }
 
        if (Properties.ContainsKey(s_propLinkCellLinkBehavior))
        {
            dataGridViewCell.LinkBehaviorInternal = LinkBehavior;
        }
 
        if (Properties.ContainsKey(s_propLinkCellLinkColor))
        {
            dataGridViewCell.LinkColorInternal = LinkColor;
        }
 
        if (Properties.ContainsKey(s_propLinkCellTrackVisitedState))
        {
            dataGridViewCell.TrackVisitedStateInternal = TrackVisitedState;
        }
 
        if (Properties.ContainsKey(s_propLinkCellVisitedLinkColor))
        {
            dataGridViewCell.VisitedLinkColorInternal = VisitedLinkColor;
        }
 
        if (_linkVisitedSet)
        {
            dataGridViewCell.LinkVisited = LinkVisited;
        }
 
        return dataGridViewCell;
    }
 
    private bool LinkBoundsContainPoint(int x, int y, int rowIndex)
    {
        Rectangle linkBounds = GetContentBounds(rowIndex);
 
        return linkBounds.Contains(x, y);
    }
 
    protected override AccessibleObject CreateAccessibilityInstance()
    {
        return new DataGridViewLinkCellAccessibleObject(this);
    }
 
    protected override Rectangle GetContentBounds(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex)
    {
        ArgumentNullException.ThrowIfNull(cellStyle);
 
        if (DataGridView is null || rowIndex < 0 || OwningColumn is null)
        {
            return Rectangle.Empty;
        }
 
        object? value = GetValue(rowIndex);
        object? formattedValue = GetFormattedValue(
            value,
            rowIndex,
            ref cellStyle,
            valueTypeConverter: null,
            formattedValueTypeConverter: null,
            DataGridViewDataErrorContexts.Formatting);
 
        ComputeBorderStyleCellStateAndCellBounds(
            rowIndex,
            out DataGridViewAdvancedBorderStyle dgvabsEffective,
            out DataGridViewElementStates cellState,
            out Rectangle cellBounds);
 
        Rectangle linkBounds = PaintPrivate(
            graphics,
            cellBounds,
            cellBounds,
            rowIndex,
            cellState,
            formattedValue,
            errorText: null,    // linkBounds is independent of errorText
            cellStyle,
            dgvabsEffective,
            DataGridViewPaintParts.ContentForeground,
            computeContentBounds: true,
            computeErrorIconBounds: false,
            paint: false);
 
#if DEBUG
        Rectangle linkBoundsDebug = PaintPrivate(
            graphics,
            cellBounds,
            cellBounds,
            rowIndex,
            cellState,
            formattedValue,
            GetErrorText(rowIndex),
            cellStyle,
            dgvabsEffective,
            DataGridViewPaintParts.ContentForeground,
            computeContentBounds: true,
            computeErrorIconBounds: false,
            paint: false);
        Debug.Assert(linkBoundsDebug.Equals(linkBounds));
#endif
 
        return linkBounds;
    }
 
    private protected override string? GetDefaultToolTipText()
    {
        if (string.IsNullOrEmpty(Value?.ToString()?.Trim(' ')) || Value is DBNull)
        {
            return SR.DefaultDataGridViewLinkCellTollTipText;
        }
 
        return null;
    }
 
    protected override Rectangle GetErrorIconBounds(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex)
    {
        ArgumentNullException.ThrowIfNull(cellStyle);
 
        if (DataGridView is null ||
            rowIndex < 0 ||
            OwningColumn is null ||
            !DataGridView.ShowCellErrors ||
            string.IsNullOrEmpty(GetErrorText(rowIndex)))
        {
            return Rectangle.Empty;
        }
 
        object? value = GetValue(rowIndex);
        object? formattedValue = GetFormattedValue(
            value,
            rowIndex,
            ref cellStyle,
            valueTypeConverter: null,
            formattedValueTypeConverter: null,
            DataGridViewDataErrorContexts.Formatting);
 
        ComputeBorderStyleCellStateAndCellBounds(
            rowIndex,
            out DataGridViewAdvancedBorderStyle dgvabsEffective,
            out DataGridViewElementStates cellState,
            out Rectangle cellBounds);
 
        Rectangle errorIconBounds = PaintPrivate(
            graphics,
            cellBounds,
            cellBounds,
            rowIndex,
            cellState,
            formattedValue,
            GetErrorText(rowIndex),
            cellStyle,
            dgvabsEffective,
            DataGridViewPaintParts.ContentForeground,
            computeContentBounds: false,
            computeErrorIconBounds: true,
            paint: false);
 
        return errorIconBounds;
    }
 
    protected override Size GetPreferredSize(
        Graphics graphics,
        DataGridViewCellStyle cellStyle,
        int rowIndex,
        Size constraintSize)
    {
        if (DataGridView is null)
        {
            return new Size(-1, -1);
        }
 
        ArgumentNullException.ThrowIfNull(cellStyle);
 
        Size preferredSize;
        Rectangle borderWidthsRect = StdBorderWidths;
        int borderAndPaddingWidths = borderWidthsRect.Left + borderWidthsRect.Width + cellStyle.Padding.Horizontal;
        int borderAndPaddingHeights = borderWidthsRect.Top + borderWidthsRect.Height + cellStyle.Padding.Vertical;
        DataGridViewFreeDimension freeDimension = GetFreeDimensionFromConstraint(constraintSize);
        object? formattedValue = GetFormattedValue(rowIndex, ref cellStyle, DataGridViewDataErrorContexts.Formatting | DataGridViewDataErrorContexts.PreferredSize);
        string? formattedString = formattedValue as string;
        if (string.IsNullOrEmpty(formattedString))
        {
            formattedString = " ";
        }
 
        TextFormatFlags flags = DataGridViewUtilities.ComputeTextFormatFlagsForCellStyleAlignment(DataGridView.RightToLeftInternal, cellStyle.Alignment, cellStyle.WrapMode);
        if (cellStyle.WrapMode == DataGridViewTriState.True && formattedString.Length > 1)
        {
            switch (freeDimension)
            {
                case DataGridViewFreeDimension.Width:
                    {
                        int maxHeight = constraintSize.Height - borderAndPaddingHeights - VerticalTextMarginTop - VerticalTextMarginBottom;
                        if ((cellStyle.Alignment & AnyBottom) != 0)
                        {
                            maxHeight--;
                        }
 
                        preferredSize = new Size(
                            MeasureTextWidth(
                                graphics,
                                formattedString,
                                cellStyle.Font!,
                                Math.Max(1, maxHeight),
                                flags),
                            0);
                        break;
                    }
 
                case DataGridViewFreeDimension.Height:
                    {
                        preferredSize = new Size(
                            0,
                            MeasureTextHeight(
                                graphics,
                                formattedString,
                                cellStyle.Font!,
                                Math.Max(1, constraintSize.Width - borderAndPaddingWidths - HorizontalTextMarginLeft - HorizontalTextMarginRight),
                                flags));
                        break;
                    }
 
                default:
                    {
                        preferredSize = MeasureTextPreferredSize(
                            graphics,
                            formattedString,
                            cellStyle.Font!,
                            5.0F,
                            flags);
                        break;
                    }
            }
        }
        else
        {
            switch (freeDimension)
            {
                case DataGridViewFreeDimension.Width:
                    {
                        preferredSize = new Size(
                            MeasureTextSize(graphics, formattedString, cellStyle.Font!, flags).Width,
                            0);
                        break;
                    }
 
                case DataGridViewFreeDimension.Height:
                    {
                        preferredSize = new Size(
                            0,
                            MeasureTextSize(graphics, formattedString, cellStyle.Font!, flags).Height);
                        break;
                    }
 
                default:
                    {
                        preferredSize = MeasureTextSize(
                            graphics,
                            formattedString,
                            cellStyle.Font!,
                            flags);
                        break;
                    }
            }
        }
 
        if (freeDimension != DataGridViewFreeDimension.Height)
        {
            preferredSize.Width += HorizontalTextMarginLeft + HorizontalTextMarginRight + borderAndPaddingWidths;
            if (DataGridView.ShowCellErrors)
            {
                // Making sure that there is enough room for the potential error icon
                preferredSize.Width = Math.Max(preferredSize.Width, borderAndPaddingWidths + IconMarginWidth * 2 + s_iconsWidth);
            }
        }
 
        if (freeDimension != DataGridViewFreeDimension.Width)
        {
            preferredSize.Height += VerticalTextMarginTop + VerticalTextMarginBottom + borderAndPaddingHeights;
            if ((cellStyle.Alignment & AnyBottom) != 0)
            {
                preferredSize.Height += VerticalTextMarginBottom;
            }
 
            if (DataGridView.ShowCellErrors)
            {
                // Making sure that there is enough room for the potential error icon
                preferredSize.Height = Math.Max(preferredSize.Height, borderAndPaddingHeights + IconMarginHeight * 2 + s_iconsHeight);
            }
        }
 
        return preferredSize;
    }
 
    protected override object? GetValue(int rowIndex)
    {
        if (UseColumnTextForLinkValue &&
            DataGridView is not null &&
            DataGridView.NewRowIndex != rowIndex &&
            OwningColumn is DataGridViewLinkColumn dataGridViewLinkColumn)
        {
            return dataGridViewLinkColumn.Text;
        }
 
        return base.GetValue(rowIndex);
    }
 
    protected override bool KeyUpUnsharesRow(KeyEventArgs e, int rowIndex)
    {
        if (e.KeyCode == Keys.Space && !e.Alt && !e.Control && !e.Shift)
        {
            return TrackVisitedState && !LinkVisited;
        }
        else
        {
            return true;
        }
    }
 
    protected override bool MouseDownUnsharesRow(DataGridViewCellMouseEventArgs e) =>
        LinkBoundsContainPoint(e.X, e.Y, e.RowIndex);
 
    protected override bool MouseLeaveUnsharesRow(int rowIndex) =>
        LinkState != LinkState.Normal;
 
    protected override bool MouseMoveUnsharesRow(DataGridViewCellMouseEventArgs e)
    {
        if (LinkBoundsContainPoint(e.X, e.Y, e.RowIndex))
        {
            if ((LinkState & LinkState.Hover) == 0)
            {
                return true;
            }
        }
        else
        {
            if ((LinkState & LinkState.Hover) != 0)
            {
                return true;
            }
        }
 
        return false;
    }
 
    protected override bool MouseUpUnsharesRow(DataGridViewCellMouseEventArgs e) =>
        TrackVisitedState && LinkBoundsContainPoint(e.X, e.Y, e.RowIndex);
 
    protected override void OnKeyUp(KeyEventArgs e, int rowIndex)
    {
        if (DataGridView is null)
        {
            return;
        }
 
        if (e.KeyCode == Keys.Space && !e.Alt && !e.Control && !e.Shift)
        {
            RaiseCellClick(new DataGridViewCellEventArgs(ColumnIndex, rowIndex));
            if (DataGridView is not null &&
                ColumnIndex < DataGridView.Columns.Count &&
                rowIndex < DataGridView.Rows.Count)
            {
                RaiseCellContentClick(new DataGridViewCellEventArgs(ColumnIndex, rowIndex));
                if (TrackVisitedState)
                {
                    LinkVisited = true;
                }
            }
 
            e.Handled = true;
        }
    }
 
    protected override void OnMouseDown(DataGridViewCellMouseEventArgs e)
    {
        if (DataGridView is null)
        {
            return;
        }
 
        if (LinkBoundsContainPoint(e.X, e.Y, e.RowIndex))
        {
            LinkState |= LinkState.Active;
            DataGridView.InvalidateCell(ColumnIndex, e.RowIndex);
        }
 
        base.OnMouseDown(e);
    }
 
    protected override void OnMouseLeave(int rowIndex)
    {
        if (DataGridView is null)
        {
            return;
        }
 
        if (s_dataGridViewCursor is not null)
        {
            DataGridView.Cursor = s_dataGridViewCursor;
            s_dataGridViewCursor = null;
        }
 
        if (LinkState != LinkState.Normal)
        {
            LinkState = LinkState.Normal;
            DataGridView.InvalidateCell(ColumnIndex, rowIndex);
        }
 
        base.OnMouseLeave(rowIndex);
    }
 
    protected override void OnMouseMove(DataGridViewCellMouseEventArgs e)
    {
        if (DataGridView is null)
        {
            return;
        }
 
        if (LinkBoundsContainPoint(e.X, e.Y, e.RowIndex))
        {
            if ((LinkState & LinkState.Hover) == 0)
            {
                LinkState |= LinkState.Hover;
                DataGridView.InvalidateCell(ColumnIndex, e.RowIndex);
            }
 
            s_dataGridViewCursor ??= DataGridView.UserSetCursor;
 
            if (DataGridView.Cursor != Cursors.Hand)
            {
                DataGridView.Cursor = Cursors.Hand;
            }
        }
        else
        {
            if ((LinkState & LinkState.Hover) != 0)
            {
                LinkState &= ~LinkState.Hover;
                DataGridView.Cursor = s_dataGridViewCursor;
                DataGridView.InvalidateCell(ColumnIndex, e.RowIndex);
            }
        }
 
        base.OnMouseMove(e);
    }
 
    protected override void OnMouseUp(DataGridViewCellMouseEventArgs e)
    {
        if (DataGridView is null)
        {
            return;
        }
 
        if (LinkBoundsContainPoint(e.X, e.Y, e.RowIndex) && TrackVisitedState)
        {
            LinkVisited = true;
        }
    }
 
    protected override void Paint(
        Graphics graphics,
        Rectangle clipBounds,
        Rectangle cellBounds,
        int rowIndex,
        DataGridViewElementStates cellState,
        object? value,
        object? formattedValue,
        string? errorText,
        DataGridViewCellStyle cellStyle,
        DataGridViewAdvancedBorderStyle advancedBorderStyle,
        DataGridViewPaintParts paintParts)
    {
        ArgumentNullException.ThrowIfNull(cellStyle);
 
        PaintPrivate(
            graphics,
            clipBounds,
            cellBounds,
            rowIndex,
            cellState,
            formattedValue,
            errorText,
            cellStyle,
            advancedBorderStyle,
            paintParts,
            computeContentBounds: false,
            computeErrorIconBounds: false,
            paint: true);
    }
 
    // PaintPrivate is used in three places that need to duplicate the paint code:
    // 1. DataGridViewCell::Paint method
    // 2. DataGridViewCell::GetContentBounds
    // 3. DataGridViewCell::GetErrorIconBounds
    //
    // if computeContentBounds is true then PaintPrivate returns the contentBounds
    // else if computeErrorIconBounds is true then PaintPrivate returns the errorIconBounds
    // else it returns Rectangle.Empty;
    private Rectangle PaintPrivate(
        Graphics g,
        Rectangle clipBounds,
        Rectangle cellBounds,
        int rowIndex,
        DataGridViewElementStates cellState,
        object? formattedValue,
        string? errorText,
        DataGridViewCellStyle cellStyle,
        DataGridViewAdvancedBorderStyle advancedBorderStyle,
        DataGridViewPaintParts paintParts,
        bool computeContentBounds,
        bool computeErrorIconBounds,
        bool paint)
    {
        // Parameter checking.
        // One bit and one bit only should be turned on
        Debug.Assert(paint || computeContentBounds || computeErrorIconBounds);
        Debug.Assert(!paint || !computeContentBounds || !computeErrorIconBounds);
        Debug.Assert(!computeContentBounds || !computeErrorIconBounds || !paint);
        Debug.Assert(!computeErrorIconBounds || !paint || !computeContentBounds);
        Debug.Assert(cellStyle is not null);
 
        if (paint && PaintBorder(paintParts))
        {
            PaintBorder(g, clipBounds, cellBounds, cellStyle, advancedBorderStyle);
        }
 
        Rectangle resultBounds = Rectangle.Empty;
 
        Rectangle borderWidths = BorderWidths(advancedBorderStyle);
        Rectangle valBounds = cellBounds;
        valBounds.Offset(borderWidths.X, borderWidths.Y);
        valBounds.Width -= borderWidths.Right;
        valBounds.Height -= borderWidths.Bottom;
 
        Point ptCurrentCell = DataGridView!.CurrentCellAddress;
        bool cellCurrent = ptCurrentCell.X == ColumnIndex && ptCurrentCell.Y == rowIndex;
        bool cellSelected = (cellState & DataGridViewElementStates.Selected) != 0;
        Color brushColor = PaintSelectionBackground(paintParts) && cellSelected
            ? cellStyle.SelectionBackColor
            : cellStyle.BackColor;
 
        if (paint && PaintBackground(paintParts) && !brushColor.HasTransparency())
        {
            using var brush = brushColor.GetCachedSolidBrushScope();
            g.FillRectangle(brush, valBounds);
        }
 
        if (cellStyle.Padding != Padding.Empty)
        {
            if (DataGridView.RightToLeftInternal)
            {
                valBounds.Offset(cellStyle.Padding.Right, cellStyle.Padding.Top);
            }
            else
            {
                valBounds.Offset(cellStyle.Padding.Left, cellStyle.Padding.Top);
            }
 
            valBounds.Width -= cellStyle.Padding.Horizontal;
            valBounds.Height -= cellStyle.Padding.Vertical;
        }
 
        Rectangle errorBounds = valBounds;
 
        if (formattedValue is string formattedValueStr && (paint || computeContentBounds))
        {
            // Font independent margins
            valBounds.Offset(HorizontalTextMarginLeft, VerticalTextMarginTop);
            valBounds.Width -= HorizontalTextMarginLeft + HorizontalTextMarginRight;
            valBounds.Height -= VerticalTextMarginTop + VerticalTextMarginBottom;
            if ((cellStyle.Alignment & AnyBottom) != 0)
            {
                valBounds.Height -= VerticalTextMarginBottom;
            }
 
            Font? getLinkFont = null;
            Font? getHoverFont = null;
            bool isActive = (LinkState & LinkState.Active) == LinkState.Active;
 
            LinkUtilities.EnsureLinkFonts(cellStyle.Font!, LinkBehavior, ref getLinkFont, ref getHoverFont, isActive);
            using Font linkFont = getLinkFont;
            using Font hoverFont = getHoverFont;
 
            TextFormatFlags flags = DataGridViewUtilities.ComputeTextFormatFlagsForCellStyleAlignment(
                DataGridView.RightToLeftInternal,
                cellStyle.Alignment,
                cellStyle.WrapMode);
 
            // Paint the focus rectangle around the link
            if (!paint)
            {
                Debug.Assert(computeContentBounds);
                resultBounds = DataGridViewUtilities.GetTextBounds(
                    valBounds,
                    formattedValueStr,
                    flags,
                    cellStyle,
                    LinkState == LinkState.Hover ? hoverFont : linkFont);
            }
            else
            {
                if (valBounds.Width > 0 && valBounds.Height > 0)
                {
                    if (cellCurrent &&
                        DataGridView.ShowFocusCues &&
                        DataGridView.Focused &&
                        PaintFocus(paintParts))
                    {
                        Rectangle focusBounds = DataGridViewUtilities.GetTextBounds(
                            valBounds,
                            formattedValueStr,
                            flags,
                            cellStyle,
                            LinkState == LinkState.Hover ? hoverFont : linkFont);
 
                        if ((cellStyle.Alignment & AnyLeft) != 0)
                        {
                            focusBounds.X--;
                            focusBounds.Width++;
                        }
                        else if ((cellStyle.Alignment & AnyRight) != 0)
                        {
                            focusBounds.X++;
                            focusBounds.Width++;
                        }
 
                        focusBounds.Height += 2;
                        ControlPaint.DrawFocusRectangle(g, focusBounds, Color.Empty, brushColor);
                    }
 
                    Color linkColor;
                    if ((LinkState & LinkState.Active) == LinkState.Active)
                    {
                        linkColor = ActiveLinkColor;
                    }
                    else if (LinkVisited)
                    {
                        linkColor = VisitedLinkColor;
                    }
                    else
                    {
                        linkColor = LinkColor;
                    }
 
                    if (PaintContentForeground(paintParts))
                    {
                        if ((flags & TextFormatFlags.SingleLine) != 0)
                        {
                            flags |= TextFormatFlags.EndEllipsis;
                        }
 
                        TextRenderer.DrawText(
                            g,
                            formattedValueStr,
                            LinkState == LinkState.Hover ? hoverFont : linkFont,
                            valBounds,
                            linkColor,
                            flags);
                    }
                }
                else if (cellCurrent &&
                    DataGridView.ShowFocusCues &&
                    DataGridView.Focused &&
                    PaintFocus(paintParts) &&
                    errorBounds.Width > 0 &&
                    errorBounds.Height > 0)
                {
                    // Draw focus rectangle
                    ControlPaint.DrawFocusRectangle(g, errorBounds, Color.Empty, brushColor);
                }
            }
        }
        else if (paint || computeContentBounds)
        {
            if (cellCurrent &&
                DataGridView.ShowFocusCues &&
                DataGridView.Focused &&
                PaintFocus(paintParts) &&
                paint &&
                valBounds.Width > 0 &&
                valBounds.Height > 0)
            {
                // Draw focus rectangle
                ControlPaint.DrawFocusRectangle(g, valBounds, Color.Empty, brushColor);
            }
        }
        else if (computeErrorIconBounds)
        {
            if (!string.IsNullOrEmpty(errorText))
            {
                resultBounds = ComputeErrorIconBounds(errorBounds);
            }
        }
 
        if (DataGridView.ShowCellErrors && paint && PaintErrorIcon(paintParts))
        {
            PaintErrorIcon(g, cellStyle, rowIndex, cellBounds, errorBounds, errorText);
        }
 
        return resultBounds;
    }
 
    public override string ToString() =>
        $"DataGridViewLinkCell {{ ColumnIndex={ColumnIndex}, RowIndex={RowIndex} }}";
}