using System.Collections.Generic;
using System.Drawing;
namespace System.Windows.Forms
{
///
/// Provides mouse functionality to RibbonTab, RibbonPanel and RibbonItem objects on a specified Control
///
public class RibbonMouseSensor : IDisposable
{
#region Fields
private RibbonItem _lastMouseDown;
#endregion
#region Constructor
///
/// Initializes inner fields
///
private RibbonMouseSensor()
{
Tabs = new List();
Panels = new List();
Items = new List();
}
///
/// Creates a new Empty Sensor
///
/// Control to listen mouse events
/// Ribbon that will be affected
public RibbonMouseSensor(Control control, Ribbon ribbon)
: this()
{
Control = control ?? throw new ArgumentNullException(nameof(control));
Ribbon = ribbon ?? throw new ArgumentNullException(nameof(ribbon));
AddHandlers();
}
///
/// Creates a new Sensor for specified objects
///
/// Control to listen mouse events
/// Ribbon that will be affected
/// Tabs that will be sensed
/// Panels that will be sensed
/// Items that will be sensed
public RibbonMouseSensor(Control control, Ribbon ribbon, IEnumerable tabs, IEnumerable panels, IEnumerable items)
: this(control, ribbon)
{
if (tabs != null) Tabs.AddRange(tabs);
if (panels != null) Panels.AddRange(panels);
if (items != null) Items.AddRange(items);
}
///
/// Creates a new Sensor for the specified RibbonTab
///
/// Control to listen to mouse events
/// Ribbon that will be affected
/// Tab that will be sensed, from which all panels and items will be extracted to sensing also.
public RibbonMouseSensor(Control control, Ribbon ribbon, RibbonTab tab)
: this(control, ribbon)
{
Tabs.Add(tab);
Panels.AddRange(tab.Panels);
foreach (RibbonPanel panel in tab.Panels)
{
Items.AddRange(panel.Items);
}
}
///
/// Creates a new Sensor for only the specified items
///
/// Control to listen to mouse events
/// Ribbon that will be affected
/// Items that will be sensed
public RibbonMouseSensor(Control control, Ribbon ribbon, IEnumerable itemsSource)
: this(control, ribbon)
{
ItemsSource = itemsSource;
/* If an item in a dropdown list (i.e. Combobox dropdown) is selected when the dropdown is shown,
* make it the last hit item. When the mouse is moved onto an item, the previous item will be deselected.
* Otherwise the initial item will never be unselected until it is moused over.
*/
foreach (RibbonItem item in itemsSource)
{
if (item.Selected)
HittedItem = item;
}
}
#endregion
#region Properties
///
/// Gets the control where the sensor listens to mouse events
///
public Control Control { get; }
///
/// Gets if the sensor has already been
///
public bool Disposed { get; private set; }
///
/// Gets the RibbonTab hitted by the last
///
internal RibbonTab HittedTab { get; set; }
///
/// Gets if the test hit resulted on some scroll button of the hitted tab
///
internal bool HittedTabScroll => HittedTabScrollLeft || HittedTabScrollRight;
///
/// Gets or sets if the last hit test resulted on the left scroll of the hitted tab
///
internal bool HittedTabScrollLeft { get; set; }
///
/// Gets or sets if the last hit test resulted on the right scroll of the hitted tab
///
internal bool HittedTabScrollRight { get; set; }
///
/// Gets the RibbonPanel hitted by the last
///
internal RibbonPanel HittedPanel { get; set; }
///
/// Gets the RibbonItem hitted by the last
///
internal RibbonItem HittedItem { get; set; }
///
/// Gets the RibbonItem (on other RibbonItem) hitted by the last
///
internal RibbonItem HittedSubItem { get; set; }
[Obsolete("use IsSuspended")]
public bool IsSupsended { get { return IsSuspended; } }
///
/// Gets if the sensor is currently suspended
///
public bool IsSuspended { get; private set; }
///
/// Gets or ests the source of items what limits the sensing.
/// If collection is null, all items on the property will be sensed.
///
public IEnumerable ItemsSource { get; set; }
///
/// Gets the collection of items this sensor affects.
///
/////@Todo: Sensing can be limitated by the property
public List Items { get; }
///
/// Gets or sets the Panel that will be the limit to be sensed.
/// If set to null, all panels in the property will be sensed.
///
public RibbonPanel PanelLimit { get; set; }
///
/// Gets the collection of panels this sensor affects.
/// Sensing can be limitated by the property
///
public List Panels { get; }
///
/// Gets the ribbon this sensor responds to
///
public Ribbon Ribbon { get; }
///
/// Gets or sets the last selected tab
///
internal RibbonTab SelectedTab { get; set; }
///
/// Gets or sets the last selected panel
///
internal RibbonPanel SelectedPanel { get; set; }
///
/// Gets or sets the last selected item
///
internal RibbonItem SelectedItem { get; set; }
///
/// Gets or sets the last selected sub-item
///
internal RibbonItem SelectedSubItem { get; set; }
///
/// Gets or sets the Tab that will be the only to be sensed.
/// If set to null, all tabs in the property will be sensed.
///
public RibbonTab TabLimit { get; set; }
///
/// Gets the collection of tabs this sensor affects.
/// Sensing can be limitated by the property
///
public List Tabs { get; }
#endregion
#region Methods
///
/// Adds the necessary handlers to the control
///
private void AddHandlers()
{
if (Control == null)
{
throw new ArgumentNullException(nameof(Control), "Control is Null, cant Add RibbonMouseSensor Handles");
}
Control.MouseMove += Control_MouseMove;
Control.MouseLeave += Control_MouseLeave;
Control.MouseDown += Control_MouseDown;
Control.MouseUp += Control_MouseUp;
Control.MouseClick += Control_MouseClick;
Control.MouseDoubleClick += Control_MouseDoubleClick;
//Control.MouseEnter
}
private void Control_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (IsSuspended || Disposed) return;
#region Panel
if (HittedPanel != null)
{
HittedPanel.OnDoubleClick(e);
}
#endregion
#region Item
if (HittedItem != null)
{
HittedItem.OnDoubleClick(e);
}
#endregion
#region SubItem
if (HittedSubItem != null)
{
HittedSubItem.OnDoubleClick(e);
}
#endregion
}
private void Control_MouseClick(object sender, MouseEventArgs e)
{
if (IsSuspended || Disposed) return;
#region Panel
if (HittedPanel != null)
{
HittedPanel.OnClick(e);
}
#endregion
#region Item
//Kevin Carbis - Added _lastMouseDown variable to track if our click originated with this control.
//Sometimes when scrolling your mouse moves off the thumb while dragging and your mouseup event
//is not the same control that you started with. This omits firing the click event on the control you
//eventually release the mouse over if it wasn't the control you started with.
if (HittedItem != null && HittedItem == _lastMouseDown)
{
////Kevin Carbis - this fixes the focus problem with textboxes when a different item is clicked
////and the edit box is still visible. This will now close the edit box for the textbox that previously
////had the focus. Otherwise the first click on a button would close the textbox and you would have to click twice.
////Control.Focus();
//if (_ribbon.ActiveTextBox != null)
// (_ribbon.ActiveTextBox as RibbonTextBox).EndEdit();
//foreach (RibbonPanel pnl in HittedItem.OwnerTab.Panels)
//{
// foreach (RibbonItem itm in pnl.Items)
// {
// if (itm is RibbonTextBox && itm != HittedItem)
// {
// RibbonTextBox txt = (RibbonTextBox)itm;
// txt.EndEdit();
// }
// }
//}
HittedItem.OnClick(e);
}
#endregion
#region SubItem
if (HittedSubItem != null)
{
HittedSubItem.OnClick(e);
}
#endregion
}
///
/// Handles the MouseUp event on the control
///
///
///
private void Control_MouseUp(object sender, MouseEventArgs e)
{
if (IsSuspended || Disposed) return;
#region Tab Scrolls
if (HittedTab != null)
{
if (HittedTab.ScrollLeftVisible)
{
HittedTab.SetScrollLeftPressed(false);
Control.Invalidate(HittedTab.ScrollLeftBounds);
}
if (HittedTab.ScrollRightVisible)
{
HittedTab.SetScrollRightPressed(false);
Control.Invalidate(HittedTab.ScrollRightBounds);
}
}
#endregion
#region Panel
if (HittedPanel != null)
{
HittedPanel.SetPressed(false);
HittedPanel.OnMouseUp(e);
Control.Invalidate(HittedPanel.Bounds);
}
#endregion
#region Item
if (HittedItem != null)
{
HittedItem.SetPressed(false);
HittedItem.OnMouseUp(e);
Control.Invalidate(HittedItem.Bounds);
}
#endregion
#region SubItem
if (HittedSubItem != null)
{
HittedSubItem.SetPressed(false);
HittedSubItem.OnMouseUp(e);
Control.Invalidate(Rectangle.Intersect(HittedItem.Bounds, HittedSubItem.Bounds));
}
#endregion
}
///
/// Handles the MouseDown on the control
///
///
///
private void Control_MouseDown(object sender, MouseEventArgs e)
{
if (IsSuspended || Disposed) return;
HitTest(e.Location);
_lastMouseDown = HittedItem;
#region Tab Scrolls
if (HittedTab != null)
{
if (HittedTabScrollLeft)
{
HittedTab.SetScrollLeftPressed(true);
Control.Invalidate(HittedTab.ScrollLeftBounds);
}
if (HittedTabScrollRight)
{
HittedTab.SetScrollRightPressed(true);
Control.Invalidate(HittedTab.ScrollRightBounds);
}
}
#endregion
#region Panel
if (HittedPanel != null)
{
HittedPanel.SetPressed(true);
HittedPanel.OnMouseDown(e);
Control.Invalidate(HittedPanel.Bounds);
}
#endregion
#region Item
if (HittedItem != null)
{
HittedItem.SetPressed(true);
HittedItem.OnMouseDown(e);
Control.Invalidate(HittedItem.Bounds);
}
#endregion
#region SubItem
if (HittedSubItem != null)
{
HittedSubItem.SetPressed(true);
HittedSubItem.OnMouseDown(e);
Control.Invalidate(Rectangle.Intersect(HittedItem.Bounds, HittedSubItem.Bounds));
}
#endregion
}
///
/// Handles the MouseLeave on the control
///
///
///
private void Control_MouseLeave(object sender, EventArgs e)
{
if (IsSuspended || Disposed) return;
}
///
/// Handles the MouseMove on the control
///
///
///
private void Control_MouseMove(object sender, MouseEventArgs e)
{
if (IsSuspended || Disposed) return;
//Console.WriteLine("MouseMove " + Control.Name);
HitTest(e.Location);
#region Selected ones
if (SelectedPanel != null && SelectedPanel != HittedPanel)
{
SelectedPanel.SetSelected(false);
SelectedPanel.OnMouseLeave(e);
Control.Invalidate(SelectedPanel.Bounds);
}
if (SelectedItem != null && SelectedItem != HittedItem)
{
SelectedItem.SetSelected(false);
SelectedItem.OnMouseLeave(e);
Control.Invalidate(SelectedItem.Bounds);
}
if (SelectedSubItem != null && SelectedSubItem != HittedSubItem)
{
SelectedSubItem.SetSelected(false);
SelectedSubItem.OnMouseLeave(e);
Control.Invalidate(Rectangle.Intersect(SelectedItem.Bounds, SelectedSubItem.Bounds));
}
#endregion
#region Tab Scrolls
if (HittedTab != null)
{
if (HittedTab.ScrollLeftVisible)
{
HittedTab.SetScrollLeftSelected(HittedTabScrollLeft);
Control.Invalidate(HittedTab.ScrollLeftBounds);
}
if (HittedTab.ScrollRightVisible)
{
HittedTab.SetScrollRightSelected(HittedTabScrollRight);
Control.Invalidate(HittedTab.ScrollRightBounds);
}
}
#endregion
#region Panel
if (HittedPanel != null)
{
if (HittedPanel == SelectedPanel)
{
HittedPanel.OnMouseMove(e);
}
else
{
HittedPanel.SetSelected(true);
HittedPanel.OnMouseEnter(e);
Control.Invalidate(HittedPanel.Bounds);
}
}
#endregion
#region Item
if (HittedItem != null)
{
if (HittedItem == SelectedItem)
{
HittedItem.OnMouseMove(e);
}
else
{
HittedItem.SetSelected(true);
HittedItem.OnMouseEnter(e);
Control.Invalidate(HittedItem.Bounds);
}
}
#endregion
#region SubItem
if (HittedSubItem != null)
{
if (HittedSubItem == SelectedSubItem)
{
HittedSubItem.OnMouseMove(e);
}
else
{
HittedSubItem.SetSelected(true);
HittedSubItem.OnMouseEnter(e);
Control.Invalidate(Rectangle.Intersect(HittedItem.Bounds, HittedSubItem.Bounds));
}
}
#endregion
}
///
/// Performs a hit-test and specifies hitted objects on properties: ,
/// , and
///
///
internal void HitTest(Point p)
{
SelectedTab = HittedTab;
SelectedPanel = HittedPanel;
SelectedItem = HittedItem;
SelectedSubItem = HittedSubItem;
HittedTab = null;
HittedTabScrollLeft = false;
HittedTabScrollRight = false;
HittedPanel = null;
HittedItem = null;
HittedSubItem = null;
#region Tabs
if (TabLimit != null && TabLimit.Visible)
{
if (TabLimit.TabContentBounds.Contains(p))
{
HittedTab = TabLimit;
}
}
else
{
foreach (RibbonTab tab in Tabs)
{
if (tab.Visible && tab.TabContentBounds.Contains(p))
{
HittedTab = tab; break;
}
}
}
#endregion
#region TabScrolls
if (HittedTab != null)
{
HittedTabScrollLeft = HittedTab.ScrollLeftVisible && HittedTab.ScrollLeftBounds.Contains(p);
HittedTabScrollRight = HittedTab.ScrollRightVisible && HittedTab.ScrollRightBounds.Contains(p);
}
#endregion
if (!HittedTabScroll)
{
#region Panels
if (PanelLimit != null && PanelLimit.Visible)
{
if (PanelLimit.Bounds.Contains(p))
{
HittedPanel = PanelLimit;
}
}
else
{
foreach (RibbonPanel pnl in Panels)
{
if (pnl.Visible && pnl.Bounds.Contains(p))
{
HittedPanel = pnl; break;
}
}
}
#endregion
#region Item
IEnumerable items = Items;
if (ItemsSource != null) items = ItemsSource;
foreach (RibbonItem item in items)
{
if (item.OwnerPanel != null && item.OwnerPanel.OverflowMode && !(Control is RibbonPanelPopup))
continue;
if (!item.Visible)
continue;
if (item.Bounds.Contains(p))
{
HittedItem = item; break;
}
}
#endregion
#region Subitem
IContainsSelectableRibbonItems container = HittedItem as IContainsSelectableRibbonItems;
IScrollableRibbonItem scrollable = HittedItem as IScrollableRibbonItem;
if (container != null)
{
Rectangle sensibleBounds = scrollable != null ? scrollable.ContentBounds : HittedItem.Bounds;
foreach (RibbonItem item in container.GetItems())
{
if (!item.Visible)
continue;
Rectangle actualBounds = item.Bounds;
actualBounds.Intersect(sensibleBounds);
if (actualBounds.Contains(p))
{
HittedSubItem = item;
}
}
}
#endregion
}
}
///
/// Removes the added handlers to the Control
///
private void RemoveHandlers()
{
//Do not Change State because if Text or Image of RibbonItem is Changed in Runtime RemoveHandlers() is called
/*
foreach (RibbonItem item in Items)
{
item.SetSelected(false);
item.SetPressed(false);
}
*/
Control.MouseMove -= Control_MouseMove;
Control.MouseLeave -= Control_MouseLeave;
Control.MouseDown -= Control_MouseDown;
Control.MouseUp -= Control_MouseUp;
// ADDED
Control.MouseClick -= Control_MouseClick;
Control.MouseDoubleClick -= Control_MouseDoubleClick;
}
///
/// Resumes the sensing after being suspended by
///
public void Resume()
{
IsSuspended = false;
}
///
/// Suspends sensing until is called
///
public void Suspend()
{
IsSuspended = true;
}
#endregion
#region IDisposable Members
///
/// Dispose
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Clean up any resources being used.
///
/// true if managed resources should be disposed; otherwise, false.
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Disposed = true;
RemoveHandlers();
}
}
#endregion
}
}