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 } }