// 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.Globalization;

namespace System.Windows.Forms;

[DefaultProperty(nameof(BindingSource))]
[DefaultEvent(nameof(RefreshItems))]
[Designer($"System.Windows.Forms.Design.BindingNavigatorDesigner, {Assemblies.SystemDesign}")]
[SRDescription(nameof(SR.DescriptionBindingNavigator))]
public class BindingNavigator : ToolStrip, ISupportInitialize
{
    private BindingSource? _bindingSource;

    private ToolStripItem? _moveFirstItem;
    private ToolStripItem? _movePreviousItem;
    private ToolStripItem? _moveNextItem;
    private ToolStripItem? _moveLastItem;
    private ToolStripItem? _addNewItem;
    private ToolStripItem? _deleteItem;
    private ToolStripItem? _positionItem;
    private ToolStripItem? _countItem;

    private string _countItemFormat = SR.BindingNavigatorCountItemFormat;

    private EventHandler? _onRefreshItems;

    private bool _initializing;

    private bool _addNewItemUserEnabled = true;
    private bool _deleteItemUserEnabled = true;

    /// <summary>
    ///  Creates an empty BindingNavigator tool strip.
    ///  Call AddStandardItems() to add standard tool strip items.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public BindingNavigator()
        : this(addStandardItems: false)
    {
    }

    /// <summary>
    ///  Creates a BindingNavigator strip containing standard items, bound to the specified BindingSource.
    /// </summary>
    public BindingNavigator(BindingSource? bindingSource)
        : this(addStandardItems: true)
    {
        BindingSource = bindingSource;
    }

    /// <summary>
    ///  Creates an empty BindingNavigator tool strip, and adds the strip to the specified container.
    ///  Call AddStandardItems() to add standard tool strip items.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public BindingNavigator(IContainer container)
        : this(addStandardItems: false)
    {
        ArgumentNullException.ThrowIfNull(container);

        container.Add(this);
    }

    /// <summary>
    ///  Creates a BindingNavigator strip, optionally containing a set of standard tool strip items.
    /// </summary>
    public BindingNavigator(bool addStandardItems)
    {
        if (addStandardItems)
        {
            AddStandardItems();
        }
    }

    /// <summary>
    ///  ISupportInitialize support. Disables updates to tool strip items during initialization.
    /// </summary>
    public void BeginInit()
    {
        _initializing = true;
    }

    /// <summary>
    ///  ISupportInitialize support. Enables updates to tool strip items after initialization.
    /// </summary>
    public void EndInit()
    {
        _initializing = false;
        RefreshItemsInternal();
    }

    /// <summary>
    ///  Unhooks the BindingNavigator from the BindingSource.
    /// </summary>
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            BindingSource = null; // ...unwires from events of any prior BindingSource
        }

        base.Dispose(disposing);
    }

    /// <summary>
    ///  Adds standard set of tool strip items to a BindingNavigator tool strip, for basic
    ///  navigation operations such as Move First, Move Next, Add New, etc.
    ///
    ///  This method is called by the Windows Form Designer when a new BindingNavigator is
    ///  added to a Form. When creating a BindingNavigator programmatically, this method
    ///  must be called explicitly.
    ///
    ///  Override this method in derived classes to define additional or alternative standard items.
    ///  To ensure optimal design-time support for your derived class, make sure each item has a
    ///  meaningful value in its Name property. At design time, this will be used to generate a unique
    ///  name for the corresponding member variable. The item's Name property will then be updated
    ///  to match the name given to the member variable.
    ///
    ///  Note: This method does NOT remove any previous items from the strip, or suspend
    ///  layout while items are being added. Those are responsibilities of the caller.
    /// </summary>
    public virtual void AddStandardItems()
    {
        //
        // Create items
        //

        MoveFirstItem = new ToolStripButton();
        MovePreviousItem = new ToolStripButton();
        MoveNextItem = new ToolStripButton();
        MoveLastItem = new ToolStripButton();
        PositionItem = new ToolStripTextBox();
        CountItem = new ToolStripLabel();
        AddNewItem = new ToolStripButton();
        DeleteItem = new ToolStripButton();

        ToolStripSeparator separator1 = new();
        ToolStripSeparator separator2 = new();
        ToolStripSeparator separator3 = new();

        //
        // Set up strings
        //

        // Default to lowercase for null name, because C# dev is more likely to create controls programmatically than
        // vb dev.
        char ch = string.IsNullOrEmpty(Name) || char.IsLower(Name[0]) ? 'b' : 'B';

        MoveFirstItem.Name = $"{ch}indingNavigatorMoveFirstItem";
        MovePreviousItem.Name = $"{ch}indingNavigatorMovePreviousItem";
        MoveNextItem.Name = $"{ch}indingNavigatorMoveNextItem";
        MoveLastItem.Name = $"{ch}indingNavigatorMoveLastItem";
        PositionItem.Name = $"{ch}indingNavigatorPositionItem";
        CountItem.Name = $"{ch}indingNavigatorCountItem";
        AddNewItem.Name = $"{ch}indingNavigatorAddNewItem";
        DeleteItem.Name = $"{ch}indingNavigatorDeleteItem";
        separator1.Name = $"{ch}indingNavigatorSeparator1";
        separator2.Name = $"{ch}indingNavigatorSeparator2";
        separator3.Name = $"{ch}indingNavigatorSeparator3";

        MoveFirstItem.Text = SR.BindingNavigatorMoveFirstItemText;
        MovePreviousItem.Text = SR.BindingNavigatorMovePreviousItemText;
        MoveNextItem.Text = SR.BindingNavigatorMoveNextItemText;
        MoveLastItem.Text = SR.BindingNavigatorMoveLastItemText;
        AddNewItem.Text = SR.BindingNavigatorAddNewItemText;
        DeleteItem.Text = SR.BindingNavigatorDeleteItemText;

        CountItem.ToolTipText = SR.BindingNavigatorCountItemTip;
        PositionItem.ToolTipText = SR.BindingNavigatorPositionItemTip;
        CountItem.AutoToolTip = false;
        PositionItem.AutoToolTip = false;

        PositionItem.AccessibleName = SR.BindingNavigatorPositionAccessibleName;

        // Set up images

        Bitmap moveFirstImage = ScaleHelper.GetIconResourceAsDefaultSizeBitmap(typeof(BindingNavigator), "BindingNavigator.MoveFirst");
        Bitmap movePreviousImage = ScaleHelper.GetIconResourceAsDefaultSizeBitmap(typeof(BindingNavigator), "BindingNavigator.MovePrevious");
        Bitmap moveNextImage = ScaleHelper.GetIconResourceAsDefaultSizeBitmap(typeof(BindingNavigator), "BindingNavigator.MoveNext");
        Bitmap moveLastImage = ScaleHelper.GetIconResourceAsDefaultSizeBitmap(typeof(BindingNavigator), "BindingNavigator.MoveLast");
        Bitmap addNewImage = ScaleHelper.GetIconResourceAsDefaultSizeBitmap(typeof(BindingNavigator), "BindingNavigator.AddNew");
        Bitmap deleteImage = ScaleHelper.GetIconResourceAsDefaultSizeBitmap(typeof(BindingNavigator), "BindingNavigator.Delete");

        MoveFirstItem.Image = moveFirstImage;
        MovePreviousItem.Image = movePreviousImage;
        MoveNextItem.Image = moveNextImage;
        MoveLastItem.Image = moveLastImage;
        AddNewItem.Image = addNewImage;
        DeleteItem.Image = deleteImage;

        MoveFirstItem.RightToLeftAutoMirrorImage = true;
        MovePreviousItem.RightToLeftAutoMirrorImage = true;
        MoveNextItem.RightToLeftAutoMirrorImage = true;
        MoveLastItem.RightToLeftAutoMirrorImage = true;
        AddNewItem.RightToLeftAutoMirrorImage = true;
        DeleteItem.RightToLeftAutoMirrorImage = true;

        MoveFirstItem.DisplayStyle = ToolStripItemDisplayStyle.Image;
        MovePreviousItem.DisplayStyle = ToolStripItemDisplayStyle.Image;
        MoveNextItem.DisplayStyle = ToolStripItemDisplayStyle.Image;
        MoveLastItem.DisplayStyle = ToolStripItemDisplayStyle.Image;
        AddNewItem.DisplayStyle = ToolStripItemDisplayStyle.Image;
        DeleteItem.DisplayStyle = ToolStripItemDisplayStyle.Image;

        //
        // Set other random properties
        //
        PositionItem.AutoSize = false;
        PositionItem.Width = 50;

        //
        // Add items to strip
        //

        Items.AddRange(
            MoveFirstItem,
            MovePreviousItem,
            separator1,
            PositionItem,
            CountItem,
            separator2,
            MoveNextItem,
            MoveLastItem,
            separator3,
            AddNewItem,
            DeleteItem);
    }

    /// <summary>
    ///  The BindingSource who's list we are currently navigating, or null.
    /// </summary>
    [DefaultValue(null)]
    [SRCategory(nameof(SR.CatData))]
    [SRDescription(nameof(SR.BindingNavigatorBindingSourcePropDescr))]
    [TypeConverter(typeof(ReferenceConverter))]
    public BindingSource? BindingSource
    {
        get
        {
            return _bindingSource;
        }

        set
        {
            WireUpBindingSource(ref _bindingSource, value);
        }
    }

    /// <summary>
    ///  The ToolStripItem that triggers the 'Move first' action, or null.
    /// </summary>
    [TypeConverter(typeof(ReferenceConverter))]
    [SRCategory(nameof(SR.CatItems))]
    [SRDescription(nameof(SR.BindingNavigatorMoveFirstItemPropDescr))]
    public ToolStripItem? MoveFirstItem
    {
        get
        {
            if (_moveFirstItem is not null && _moveFirstItem.IsDisposed)
            {
                _moveFirstItem = null;
            }

            return _moveFirstItem;
        }

        set
        {
            WireUpButton(ref _moveFirstItem, value, OnMoveFirst);
        }
    }

    /// <summary>
    ///  The ToolStripItem that triggers the 'Move previous' action, or null.
    /// </summary>
    [TypeConverter(typeof(ReferenceConverter))]
    [SRCategory(nameof(SR.CatItems))]
    [SRDescription(nameof(SR.BindingNavigatorMovePreviousItemPropDescr))]
    public ToolStripItem? MovePreviousItem
    {
        get
        {
            if (_movePreviousItem is not null && _movePreviousItem.IsDisposed)
            {
                _movePreviousItem = null;
            }

            return _movePreviousItem;
        }

        set
        {
            WireUpButton(ref _movePreviousItem, value, OnMovePrevious);
        }
    }

    /// <summary>
    ///  The ToolStripItem that triggers the 'Move next' action, or null.
    /// </summary>
    [TypeConverter(typeof(ReferenceConverter))]
    [SRCategory(nameof(SR.CatItems))]
    [SRDescription(nameof(SR.BindingNavigatorMoveNextItemPropDescr))]
    public ToolStripItem? MoveNextItem
    {
        get
        {
            if (_moveNextItem is not null && _moveNextItem.IsDisposed)
            {
                _moveNextItem = null;
            }

            return _moveNextItem;
        }

        set
        {
            WireUpButton(ref _moveNextItem, value, OnMoveNext);
        }
    }

    /// <summary>
    ///  The ToolStripItem that triggers the 'Move last' action, or null.
    /// </summary>
    [TypeConverter(typeof(ReferenceConverter))]
    [SRCategory(nameof(SR.CatItems))]
    [SRDescription(nameof(SR.BindingNavigatorMoveLastItemPropDescr))]
    public ToolStripItem? MoveLastItem
    {
        get
        {
            if (_moveLastItem is not null && _moveLastItem.IsDisposed)
            {
                _moveLastItem = null;
            }

            return _moveLastItem;
        }

        set
        {
            WireUpButton(ref _moveLastItem, value, OnMoveLast);
        }
    }

    /// <summary>
    ///  The ToolStripItem that triggers the 'Add new' action, or null.
    /// </summary>
    [TypeConverter(typeof(ReferenceConverter))]
    [SRCategory(nameof(SR.CatItems))]
    [SRDescription(nameof(SR.BindingNavigatorAddNewItemPropDescr))]
    public ToolStripItem? AddNewItem
    {
        get
        {
            if (_addNewItem is not null && _addNewItem.IsDisposed)
            {
                _addNewItem = null;
            }

            return _addNewItem;
        }

        set
        {
            if (_addNewItem != value && value is not null)
            {
                value.InternalEnabledChanged += OnAddNewItemEnabledChanged;
                _addNewItemUserEnabled = value.Enabled;
            }

            WireUpButton(ref _addNewItem, value, OnAddNew);
        }
    }

    /// <summary>
    ///  The ToolStripItem that triggers the 'Delete' action, or null.
    /// </summary>
    [TypeConverter(typeof(ReferenceConverter))]
    [SRCategory(nameof(SR.CatItems))]
    [SRDescription(nameof(SR.BindingNavigatorDeleteItemPropDescr))]
    public ToolStripItem? DeleteItem
    {
        get
        {
            if (_deleteItem is not null && _deleteItem.IsDisposed)
            {
                _deleteItem = null;
            }

            return _deleteItem;
        }

        set
        {
            if (_deleteItem != value && value is not null)
            {
                value.InternalEnabledChanged += OnDeleteItemEnabledChanged;
                _deleteItemUserEnabled = value.Enabled;
            }

            WireUpButton(ref _deleteItem, value, OnDelete);
        }
    }

    /// <summary>
    ///  The ToolStripItem that displays the current position, or null.
    /// </summary>
    [TypeConverter(typeof(ReferenceConverter))]
    [SRCategory(nameof(SR.CatItems))]
    [SRDescription(nameof(SR.BindingNavigatorPositionItemPropDescr))]
    public ToolStripItem? PositionItem
    {
        get
        {
            if (_positionItem is not null && _positionItem.IsDisposed)
            {
                _positionItem = null;
            }

            return _positionItem;
        }

        set
        {
            WireUpTextBox(ref _positionItem, value, OnPositionKey, OnPositionLostFocus);
        }
    }

    /// <summary>
    ///  The ToolStripItem that displays the total number of items, or null.
    /// </summary>
    [TypeConverter(typeof(ReferenceConverter))]
    [SRCategory(nameof(SR.CatItems))]
    [SRDescription(nameof(SR.BindingNavigatorCountItemPropDescr))]
    public ToolStripItem? CountItem
    {
        get
        {
            if (_countItem is not null && _countItem.IsDisposed)
            {
                _countItem = null;
            }

            return _countItem;
        }
        set
        {
            WireUpLabel(ref _countItem, value);
        }
    }

    /// <summary>
    ///  Formatting to apply to count displayed in the CountItem tool strip item.
    /// </summary>
    [SRCategory(nameof(SR.CatAppearance))]
    [SRDescription(nameof(SR.BindingNavigatorCountItemFormatPropDescr))]
    public string CountItemFormat
    {
        get
        {
            return _countItemFormat;
        }
        set
        {
            if (_countItemFormat != value)
            {
                _countItemFormat = value;
                RefreshItemsInternal();
            }
        }
    }

    /// <summary>
    ///  Event raised when the state of the tool strip items needs to be
    ///  refreshed to reflect the current state of the data.
    /// </summary>
    [SRCategory(nameof(SR.CatBehavior))]
    [SRDescription(nameof(SR.BindingNavigatorRefreshItemsEventDescr))]
    public event EventHandler? RefreshItems
    {
        add => _onRefreshItems += value;
        remove => _onRefreshItems -= value;
    }

    /// <summary>
    ///  Refreshes the state of the standard items to reflect the current state of the data.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void RefreshItemsCore()
    {
        int count, position;
        bool allowNew, allowRemove;

        // Get state info from the binding source (if any)
        if (_bindingSource is null)
        {
            count = 0;
            position = 0;
            allowNew = false;
            allowRemove = false;
        }
        else
        {
            count = _bindingSource.Count;
            position = _bindingSource.Position + 1;
            allowNew = (_bindingSource as IBindingList).AllowNew;
            allowRemove = (_bindingSource as IBindingList).AllowRemove;
        }

        // Enable or disable items (except when in design mode)
        if (!DesignMode)
        {
            if (MoveFirstItem is not null)
            {
                _moveFirstItem!.Enabled = (position > 1);
            }

            if (MovePreviousItem is not null)
            {
                _movePreviousItem!.Enabled = (position > 1);
            }

            if (MoveNextItem is not null)
            {
                _moveNextItem!.Enabled = (position < count);
            }

            if (MoveLastItem is not null)
            {
                _moveLastItem!.Enabled = (position < count);
            }

            if (AddNewItem is not null)
            {
                EventHandler handler = new(OnAddNewItemEnabledChanged);
                _addNewItem!.InternalEnabledChanged -= handler;
                _addNewItem.Enabled = (_addNewItemUserEnabled && allowNew);
                _addNewItem.InternalEnabledChanged += handler;
            }

            if (DeleteItem is not null)
            {
                EventHandler handler = new(OnDeleteItemEnabledChanged);
                _deleteItem!.InternalEnabledChanged -= handler;
                _deleteItem.Enabled = (_deleteItemUserEnabled && allowRemove && count > 0);
                _deleteItem.InternalEnabledChanged += handler;
            }

            if (PositionItem is not null)
            {
                _positionItem!.Enabled = (position > 0 && count > 0);
            }

            if (CountItem is not null)
            {
                _countItem!.Enabled = (count > 0);
            }
        }

        // Update current position indicator
        _positionItem?.Text = position.ToString(CultureInfo.CurrentCulture);

        // Update record count indicator
        _countItem?.Text = DesignMode ? CountItemFormat : string.Format(CultureInfo.CurrentCulture, CountItemFormat, count);
    }

    /// <summary>
    ///  Called when the state of the tool strip items needs to be refreshed to reflect the current state of the data.
    ///  Calls <see cref="RefreshItemsCore"/> to refresh the state of the standard items, then raises the RefreshItems event.
    /// </summary>
    protected virtual void OnRefreshItems()
    {
        // Refresh all the standard items
        RefreshItemsCore();

        // Raise the public event
        _onRefreshItems?.Invoke(this, EventArgs.Empty);
    }

    /// <summary>
    ///  Triggers form validation. Used by the BindingNavigator's standard items when clicked. If a validation error occurs
    ///  on the form, focus remains on the active control and the standard item does not perform its standard click action.
    ///  Custom items may also use this method to trigger form validation and check for success before performing an action.
    /// </summary>
    public bool Validate()
    {
        return ValidateActiveControl(out _);
    }

    /// <summary>
    ///  Accept new row position entered into PositionItem.
    /// </summary>
    private void AcceptNewPosition()
    {
        // If no position item or binding source, do nothing
        if (_positionItem is null || _bindingSource is null)
        {
            return;
        }

        // Default to old position, in case new position turns out to be garbage
        int newPosition = _bindingSource.Position;

        try
        {
            // Read new position from item text (and subtract one!)
            newPosition = Convert.ToInt32(_positionItem.Text, CultureInfo.CurrentCulture) - 1;
        }
        catch (FormatException)
        {
            // Ignore bad user input
        }
        catch (OverflowException)
        {
            // Ignore bad user input
        }

        // If user has managed to enter a valid number, that is not the same as the current position, try
        // navigating to that position. Let the BindingSource validate the new position to keep it in range.
        if (newPosition != _bindingSource.Position)
        {
            _bindingSource.Position = newPosition;
        }

        // Update state of all items to reflect final position. If the user entered a bad position,
        // this will effectively reset the Position item back to showing the current position.
        RefreshItemsInternal();
    }

    /// <summary>
    ///  Cancel new row position entered into PositionItem.
    /// </summary>
    private void CancelNewPosition()
    {
        // Just refresh state of all items to reflect current position
        // (causing position item's new value to get blasted away)
        RefreshItemsInternal();
    }

    /// <summary>
    ///  Navigates to first item in BindingSource's list when the MoveFirstItem is clicked.
    /// </summary>
    private void OnMoveFirst(object? sender, EventArgs e)
    {
        if (Validate())
        {
            if (_bindingSource is not null)
            {
                _bindingSource.MoveFirst();
                RefreshItemsInternal();
            }
        }
    }

    /// <summary>
    ///  Navigates to previous item in BindingSource's list when the MovePreviousItem is clicked.
    /// </summary>
    private void OnMovePrevious(object? sender, EventArgs e)
    {
        if (Validate())
        {
            if (_bindingSource is not null)
            {
                _bindingSource.MovePrevious();
                RefreshItemsInternal();
            }
        }
    }

    /// <summary>
    ///  Navigates to next item in BindingSource's list when the MoveNextItem is clicked.
    /// </summary>
    private void OnMoveNext(object? sender, EventArgs e)
    {
        if (Validate())
        {
            if (_bindingSource is not null)
            {
                _bindingSource.MoveNext();
                RefreshItemsInternal();
            }
        }
    }

    /// <summary>
    ///  Navigates to last item in BindingSource's list when the MoveLastItem is clicked.
    /// </summary>
    private void OnMoveLast(object? sender, EventArgs e)
    {
        if (Validate())
        {
            if (_bindingSource is not null)
            {
                _bindingSource.MoveLast();
                RefreshItemsInternal();
            }
        }
    }

    /// <summary>
    ///  Adds new item to BindingSource's list when the AddNewItem is clicked.
    /// </summary>
    private void OnAddNew(object? sender, EventArgs e)
    {
        if (Validate())
        {
            if (_bindingSource is not null)
            {
                _bindingSource.AddNew();
                RefreshItemsInternal();
            }
        }
    }

    /// <summary>
    ///  Deletes current item from BindingSource's list when the DeleteItem is clicked.
    /// </summary>
    private void OnDelete(object? sender, EventArgs e)
    {
        if (Validate())
        {
            if (_bindingSource is not null)
            {
                _bindingSource.RemoveCurrent();
                RefreshItemsInternal();
            }
        }
    }

    /// <summary>
    ///  Navigates to specific item in BindingSource's list when a value is entered into the PositionItem.
    /// </summary>
    private void OnPositionKey(object? sender, KeyEventArgs e)
    {
        switch (e.KeyCode)
        {
            case Keys.Enter:
                AcceptNewPosition();
                break;
            case Keys.Escape:
                CancelNewPosition();
                break;
        }
    }

    /// <summary>
    ///  Navigates to specific item in BindingSource's list when a value is entered into the PositionItem.
    /// </summary>
    private void OnPositionLostFocus(object? sender, EventArgs e)
    {
        AcceptNewPosition();
    }

    /// <summary>
    ///  Refresh tool strip items when something changes in the BindingSource.
    /// </summary>
    private void OnBindingSourceStateChanged(object? sender, EventArgs e)
    {
        RefreshItemsInternal();
    }

    /// <summary>
    ///  Refresh tool strip items when the BindingSource is disposed.
    /// </summary>
    private void OnBindingSourceDisposed(object? sender, EventArgs e)
    {
        BindingSource = null;
    }

    /// <summary>
    ///  Refresh tool strip items when something changes in the BindingSource's list.
    /// </summary>
    private void OnBindingSourceListChanged(object? sender, ListChangedEventArgs e)
    {
        RefreshItemsInternal();
    }

    /// <summary>
    ///  Refresh the state of the items when the state of the data changes.
    /// </summary>
    private void RefreshItemsInternal()
    {
        // Block all updates during initialization
        if (_initializing)
        {
            return;
        }

        // Call method that updates the items (overridable)
        OnRefreshItems();
    }

    private void ResetCountItemFormat()
    {
        _countItemFormat = SR.BindingNavigatorCountItemFormat;
    }

    private bool ShouldSerializeCountItemFormat()
    {
        return _countItemFormat != SR.BindingNavigatorCountItemFormat;
    }

    private void OnAddNewItemEnabledChanged(object? sender, EventArgs e)
    {
        if (AddNewItem is not null)
        {
            _addNewItemUserEnabled = _addNewItem!.Enabled;
        }
    }

    private void OnDeleteItemEnabledChanged(object? sender, EventArgs e)
    {
        if (DeleteItem is not null)
        {
            _deleteItemUserEnabled = _deleteItem!.Enabled;
        }
    }

    /// <summary>
    ///  Wire up some member variable to the specified button item, hooking events
    ///  on the new button and unhooking them from the previous button, if required.
    /// </summary>
    private void WireUpButton(ref ToolStripItem? oldButton, ToolStripItem? newButton, EventHandler clickHandler)
    {
        if (oldButton == newButton)
        {
            return;
        }

        oldButton?.Click -= clickHandler;
        newButton?.Click += clickHandler;
        oldButton = newButton;
        RefreshItemsInternal();
    }

    /// <summary>
    ///  Wire up some member variable to the specified text box item, hooking events
    ///  on the new text box and unhooking them from the previous text box, if required.
    /// </summary>
    private void WireUpTextBox(ref ToolStripItem? oldTextBox, ToolStripItem? newTextBox, KeyEventHandler keyUpHandler, EventHandler lostFocusHandler)
    {
        if (oldTextBox != newTextBox)
        {
            if (oldTextBox is ToolStripControlHost oldCtrl)
            {
                oldCtrl.KeyUp -= keyUpHandler;
                oldCtrl.LostFocus -= lostFocusHandler;
            }

            if (newTextBox is ToolStripControlHost newCtrl)
            {
                newCtrl.KeyUp += keyUpHandler;
                newCtrl.LostFocus += lostFocusHandler;
            }

            oldTextBox = newTextBox;
            RefreshItemsInternal();
        }
    }

    /// <summary>
    ///  Wire up some member variable to the specified label item, hooking events
    ///  on the new label and unhooking them from the previous label, if required.
    /// </summary>
    private void WireUpLabel(ref ToolStripItem? oldLabel, ToolStripItem? newLabel)
    {
        if (oldLabel != newLabel)
        {
            oldLabel = newLabel;
            RefreshItemsInternal();
        }
    }

    /// <summary>
    ///  Wire up some member variable to the specified binding source, hooking events
    ///  on the new binding source and unhooking them from the previous one, if required.
    /// </summary>
    private void WireUpBindingSource(ref BindingSource? oldBindingSource, BindingSource? newBindingSource)
    {
        if (oldBindingSource != newBindingSource)
        {
            if (oldBindingSource is not null)
            {
                oldBindingSource.PositionChanged -= OnBindingSourceStateChanged;
                oldBindingSource.CurrentChanged -= OnBindingSourceStateChanged;
                oldBindingSource.CurrentItemChanged -= OnBindingSourceStateChanged;
                oldBindingSource.DataSourceChanged -= OnBindingSourceStateChanged;
                oldBindingSource.DataMemberChanged -= OnBindingSourceStateChanged;
                oldBindingSource.ListChanged -= OnBindingSourceListChanged;
                oldBindingSource.Disposed -= OnBindingSourceDisposed;
            }

            if (newBindingSource is not null)
            {
                newBindingSource.PositionChanged += OnBindingSourceStateChanged;
                newBindingSource.CurrentChanged += OnBindingSourceStateChanged;
                newBindingSource.CurrentItemChanged += OnBindingSourceStateChanged;
                newBindingSource.DataSourceChanged += OnBindingSourceStateChanged;
                newBindingSource.DataMemberChanged += OnBindingSourceStateChanged;
                newBindingSource.ListChanged += OnBindingSourceListChanged;
                newBindingSource.Disposed += OnBindingSourceDisposed;
            }

            oldBindingSource = newBindingSource;
            RefreshItemsInternal();
        }
    }
}
