using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; namespace Cyotek.Windows.Forms.Demo { // Cyotek ImageBox // Copyright (c) 2010-2015 Cyotek Ltd. // http://cyotek.com // http://cyotek.com/blog/tag/imagebox // Licensed under the MIT License. See license.txt for the full text. // If you use this control in your applications, attribution, donations or contributions are welcome. internal class ImageBoxEx : ImageBox { #region Constants private readonly DragHandleCollection _dragHandles; private static readonly object _eventDragHandleSizeChanged = new object(); private static readonly object _eventMaximumSelectionSizeChanged = new object(); private static readonly object _eventMinimumSelectionSizeChanged = new object(); private static readonly object _eventSelectionMoved = new object(); private static readonly object _eventSelectionMoving = new object(); private static readonly object _eventSelectionResized = new object(); private static readonly object _eventSelectionResizing = new object(); #endregion #region Fields private int _dragHandleSize; private Point _dragOrigin; private Point _dragOriginOffset; private bool _isMoving; private bool _isResizing; private Size _maximumSelectionSize; private Size _minimumSelectionSize; private RectangleF _previousSelectionRegion; private DragHandleAnchor _resizeAnchor; #endregion #region Constructors public ImageBoxEx() { _dragHandles = new DragHandleCollection(); _dragHandleSize = 8; _maximumSelectionSize = Size.Empty; this.PositionDragHandles(); } #endregion #region Events /// /// Occurs when the DragHandleSize property value changes /// [Category("Property Changed")] public event EventHandler DragHandleSizeChanged { add { this.Events.AddHandler(_eventDragHandleSizeChanged, value); } remove { this.Events.RemoveHandler(_eventDragHandleSizeChanged, value); } } /// /// Occurs when the MaximumSelectionSize property value changes /// [Category("Property Changed")] public event EventHandler MaximumSelectionSizeChanged { add { this.Events.AddHandler(_eventMaximumSelectionSizeChanged, value); } remove { this.Events.RemoveHandler(_eventMaximumSelectionSizeChanged, value); } } /// /// Occurs when the MinimumSelectionSize property value changes /// [Category("Property Changed")] public event EventHandler MinimumSelectionSizeChanged { add { this.Events.AddHandler(_eventMinimumSelectionSizeChanged, value); } remove { this.Events.RemoveHandler(_eventMinimumSelectionSizeChanged, value); } } [Category("Action")] public event EventHandler SelectionMoved { add { this.Events.AddHandler(_eventSelectionMoved, value); } remove { this.Events.RemoveHandler(_eventSelectionMoved, value); } } [Category("Action")] public event CancelEventHandler SelectionMoving { add { this.Events.AddHandler(_eventSelectionMoving, value); } remove { this.Events.RemoveHandler(_eventSelectionMoving, value); } } [Category("Action")] public event EventHandler SelectionResized { add { this.Events.AddHandler(_eventSelectionResized, value); } remove { this.Events.RemoveHandler(_eventSelectionResized, value); } } [Category("Action")] public event CancelEventHandler SelectionResizing { add { this.Events.AddHandler(_eventSelectionResizing, value); } remove { this.Events.RemoveHandler(_eventSelectionResizing, value); } } #endregion #region Properties [Browsable(false)] public DragHandleCollection DragHandles { get { return _dragHandles; } } [Category("Appearance")] [DefaultValue(8)] public virtual int DragHandleSize { get { return _dragHandleSize; } set { if (_dragHandleSize != value) { _dragHandleSize = value; this.OnDragHandleSizeChanged(EventArgs.Empty); } } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsMoving { get { return _isMoving; } protected set { _isMoving = value; } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsResizing { get { return _isResizing; } protected set { _isResizing = value; } } [Category("Behavior")] [DefaultValue(typeof(Size), "0, 0")] public virtual Size MaximumSelectionSize { get { return _maximumSelectionSize; } set { if (this.MaximumSelectionSize != value) { _maximumSelectionSize = value; this.OnMaximumSelectionSizeChanged(EventArgs.Empty); } } } [Category("Behavior")] [DefaultValue(typeof(Size), "0, 0")] public virtual Size MinimumSelectionSize { get { return _minimumSelectionSize; } set { if (this.MinimumSelectionSize != value) { _minimumSelectionSize = value; this.OnMinimumSelectionSizeChanged(EventArgs.Empty); } } } [Browsable(false)] public RectangleF PreviousSelectionRegion { get { return _previousSelectionRegion; } protected set { _previousSelectionRegion = value; } } protected Point DragOrigin { get { return _dragOrigin; } set { _dragOrigin = value; } } protected Point DragOriginOffset { get { return _dragOriginOffset; } set { _dragOriginOffset = value; } } protected DragHandleAnchor ResizeAnchor { get { return _resizeAnchor; } set { _resizeAnchor = value; } } #endregion #region Methods public void CancelResize() { this.SelectionRegion = _previousSelectionRegion; this.CompleteResize(); } public void StartMove() { CancelEventArgs e; if (_isMoving || _isResizing) { throw new InvalidOperationException("A move or resize action is currently being performed."); } e = new CancelEventArgs(); this.OnSelectionMoving(e); if (!e.Cancel) { _previousSelectionRegion = this.SelectionRegion; _isMoving = true; } } protected virtual void DrawDragHandle(Graphics graphics, DragHandle handle) { int left; int top; int width; int height; Pen outerPen; Brush innerBrush; left = handle.Bounds.Left; top = handle.Bounds.Top; width = handle.Bounds.Width; height = handle.Bounds.Height; if (handle.Enabled) { outerPen = SystemPens.WindowFrame; innerBrush = SystemBrushes.Window; } else { outerPen = SystemPens.ControlDark; innerBrush = SystemBrushes.Control; } graphics.FillRectangle(innerBrush, left + 1, top + 1, width - 2, height - 2); graphics.DrawLine(outerPen, left + 1, top, left + width - 2, top); graphics.DrawLine(outerPen, left, top + 1, left, top + height - 2); graphics.DrawLine(outerPen, left + 1, top + height - 1, left + width - 2, top + height - 1); graphics.DrawLine(outerPen, left + width - 1, top + 1, left + width - 1, top + height - 2); } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnDragHandleSizeChanged(EventArgs e) { EventHandler handler; this.PositionDragHandles(); this.Invalidate(); handler = (EventHandler)this.Events[_eventDragHandleSizeChanged]; handler?.Invoke(this, e); } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnMaximumSelectionSizeChanged(EventArgs e) { EventHandler handler; handler = (EventHandler)this.Events[_eventMaximumSelectionSizeChanged]; handler?.Invoke(this, e); } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnMinimumSelectionSizeChanged(EventArgs e) { EventHandler handler; handler = (EventHandler)this.Events[_eventMinimumSelectionSizeChanged]; handler?.Invoke(this, e); } /// /// Raises the event. /// /// /// A that contains the event data. /// protected override void OnMouseDown(MouseEventArgs e) { Point imagePoint; RectangleF selectionRegion; imagePoint = this.PointToImage(e.Location); selectionRegion = this.SelectionRegion; if (e.Button == MouseButtons.Left && (selectionRegion.Contains(imagePoint) || this.HitTest(e.Location) != DragHandleAnchor.None)) { _dragOrigin = e.Location; _dragOriginOffset = new Point(imagePoint.X - (int)selectionRegion.X, imagePoint.Y - (int)selectionRegion.Y); } else { _dragOriginOffset = Point.Empty; _dragOrigin = Point.Empty; } base.OnMouseDown(e); } /// /// Raises the event. /// /// /// A that contains the event data. /// protected override void OnMouseMove(MouseEventArgs e) { // start either a move or a resize operation if (!this.IsSelecting && !_isMoving && !_isResizing && e.Button == MouseButtons.Left && !_dragOrigin.IsEmpty && this.IsOutsideDragZone(e.Location)) { DragHandleAnchor anchor; anchor = this.HitTest(_dragOrigin); if (anchor == DragHandleAnchor.None) { // move this.StartMove(); } else if (_dragHandles[anchor]. Enabled && _dragHandles[anchor]. Visible) { // resize this.StartResize(anchor); } } // set the cursor this.SetCursor(e.Location); // perform operations this.ProcessSelectionMove(e.Location); this.ProcessSelectionResize(e.Location); base.OnMouseMove(e); } /// /// Raises the event. /// /// /// A that contains the event data. /// protected override void OnMouseUp(MouseEventArgs e) { if (_isMoving) { this.CompleteMove(); } else if (_isResizing) { this.CompleteResize(); } base.OnMouseUp(e); } /// /// Raises the event. /// /// /// A that contains the event data. /// protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (this.AllowPainting && !this.SelectionRegion.IsEmpty) { foreach (DragHandle handle in _dragHandles) { if (handle.Visible) { this.DrawDragHandle(e.Graphics, handle); } } } } /// /// Raises the event. /// /// /// The instance containing the event data. /// protected override void OnPanStart(CancelEventArgs e) { if (_isMoving || _isResizing || !_dragOrigin.IsEmpty) { e.Cancel = true; } base.OnPanStart(e); } /// /// Raises the event. /// /// /// An that contains the event data. /// protected override void OnResize(EventArgs e) { base.OnResize(e); this.PositionDragHandles(); } /// /// Raises the event. /// /// /// A that contains the event data. /// protected override void OnScroll(ScrollEventArgs se) { base.OnScroll(se); this.PositionDragHandles(); } /// /// Raises the event. /// /// /// The instance containing the event data. /// protected override void OnSelecting(ImageBoxCancelEventArgs e) { e.Cancel = _isMoving || _isResizing || this.SelectionRegion.Contains(this.PointToImage(e.Location)) || this.HitTest(e.Location) != DragHandleAnchor.None; base.OnSelecting(e); } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnSelectionMoved(EventArgs e) { EventHandler handler; handler = (EventHandler)this.Events[_eventSelectionMoved]; handler?.Invoke(this, e); } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnSelectionMoving(CancelEventArgs e) { CancelEventHandler handler; handler = (CancelEventHandler)this.Events[_eventSelectionMoving]; handler?.Invoke(this, e); } /// /// Raises the event. /// /// /// The instance containing the event data. /// protected override void OnSelectionRegionChanged(EventArgs e) { base.OnSelectionRegionChanged(e); this.PositionDragHandles(); } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnSelectionResized(EventArgs e) { EventHandler handler; handler = (EventHandler)this.Events[_eventSelectionResized]; handler?.Invoke(this, e); } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnSelectionResizing(CancelEventArgs e) { CancelEventHandler handler; handler = (CancelEventHandler)this.Events[_eventSelectionResizing]; handler?.Invoke(this, e); } /// /// Raises the event. /// /// /// The instance containing the event data. /// protected override void OnZoomChanged(EventArgs e) { base.OnZoomChanged(e); this.PositionDragHandles(); } /// /// Processes a dialog key. /// /// /// true if the key was processed by the control; otherwise, false. /// /// One of the values that represents the key to process. protected override bool ProcessDialogKey(Keys keyData) { bool result; if (keyData == Keys.Escape && (_isResizing || _isMoving)) { if (_isResizing) { this.CancelResize(); } else { this.CancelMove(); } result = true; } else { result = base.ProcessDialogKey(keyData); } return result; } protected virtual void SetCursor(Point point) { // http://forums.cyotek.com/imagebox/cursor-issue-in-imageboxex/msg92/#msg92 if (!this.IsPanning) { Cursor cursor; if (this.IsSelecting) { cursor = Cursors.Default; } else { DragHandleAnchor handleAnchor; handleAnchor = _isResizing ? _resizeAnchor : this.HitTest(point); if (handleAnchor != DragHandleAnchor.None && _dragHandles[handleAnchor]. Enabled) { switch (handleAnchor) { case DragHandleAnchor.TopLeft: case DragHandleAnchor.BottomRight: cursor = Cursors.SizeNWSE; break; case DragHandleAnchor.TopCenter: case DragHandleAnchor.BottomCenter: cursor = Cursors.SizeNS; break; case DragHandleAnchor.TopRight: case DragHandleAnchor.BottomLeft: cursor = Cursors.SizeNESW; break; case DragHandleAnchor.MiddleLeft: case DragHandleAnchor.MiddleRight: cursor = Cursors.SizeWE; break; default: throw new ArgumentOutOfRangeException(); } } else if (_isMoving || this.SelectionRegion.Contains(this.PointToImage(point))) { cursor = Cursors.SizeAll; } else { cursor = Cursors.Default; } } this.Cursor = cursor; } } private void CancelMove() { this.SelectionRegion = _previousSelectionRegion; this.CompleteMove(); } private void CompleteMove() { this.ResetDrag(); this.OnSelectionMoved(EventArgs.Empty); } private void CompleteResize() { this.ResetDrag(); this.OnSelectionResized(EventArgs.Empty); } private DragHandleAnchor HitTest(Point cursorPosition) { return _dragHandles.HitTest(cursorPosition); } private bool IsOutsideDragZone(Point location) { Rectangle dragZone; int dragWidth; int dragHeight; dragWidth = SystemInformation.DragSize.Width; dragHeight = SystemInformation.DragSize.Height; dragZone = new Rectangle(_dragOrigin.X - (dragWidth / 2), _dragOrigin.Y - (dragHeight / 2), dragWidth, dragHeight); return !dragZone.Contains(location); } private void PositionDragHandles() { if (_dragHandles != null && _dragHandleSize > 0) { RectangleF selectionRegion; selectionRegion = this.SelectionRegion; if (selectionRegion.IsEmpty) { foreach (DragHandle handle in _dragHandles) { handle.Bounds = Rectangle.Empty; } } else { int left; int top; int right; int bottom; int halfWidth; int halfHeight; int halfDragHandleSize; Rectangle viewport; int offsetX; int offsetY; viewport = this.GetImageViewPort(); offsetX = viewport.Left + this.Padding.Left + this.AutoScrollPosition.X; offsetY = viewport.Top + this.Padding.Top + this.AutoScrollPosition.Y; halfDragHandleSize = _dragHandleSize / 2; left = Convert.ToInt32((selectionRegion.Left * this.ZoomFactor) + offsetX); top = Convert.ToInt32((selectionRegion.Top * this.ZoomFactor) + offsetY); right = left + Convert.ToInt32(selectionRegion.Width * this.ZoomFactor); bottom = top + Convert.ToInt32(selectionRegion.Height * this.ZoomFactor); halfWidth = Convert.ToInt32(selectionRegion.Width * this.ZoomFactor) / 2; halfHeight = Convert.ToInt32(selectionRegion.Height * this.ZoomFactor) / 2; _dragHandles[DragHandleAnchor.TopLeft]. Bounds = new Rectangle(left - _dragHandleSize, top - _dragHandleSize, _dragHandleSize, _dragHandleSize); _dragHandles[DragHandleAnchor.TopCenter]. Bounds = new Rectangle(left + halfWidth - halfDragHandleSize, top - _dragHandleSize, _dragHandleSize, _dragHandleSize); _dragHandles[DragHandleAnchor.TopRight]. Bounds = new Rectangle(right, top - _dragHandleSize, _dragHandleSize, _dragHandleSize); _dragHandles[DragHandleAnchor.MiddleLeft]. Bounds = new Rectangle(left - _dragHandleSize, top + halfHeight - halfDragHandleSize, _dragHandleSize, _dragHandleSize); _dragHandles[DragHandleAnchor.MiddleRight]. Bounds = new Rectangle(right, top + halfHeight - halfDragHandleSize, _dragHandleSize, _dragHandleSize); _dragHandles[DragHandleAnchor.BottomLeft]. Bounds = new Rectangle(left - _dragHandleSize, bottom, _dragHandleSize, _dragHandleSize); _dragHandles[DragHandleAnchor.BottomCenter]. Bounds = new Rectangle(left + halfWidth - halfDragHandleSize, bottom, _dragHandleSize, _dragHandleSize); _dragHandles[DragHandleAnchor.BottomRight]. Bounds = new Rectangle(right, bottom, _dragHandleSize, _dragHandleSize); } } } private void ProcessSelectionMove(Point cursorPosition) { if (_isMoving) { int x; int y; Point imagePoint; Size viewSize; RectangleF selectionRegion; imagePoint = this.PointToImage(cursorPosition, false); viewSize = this.ViewSize; selectionRegion = this.SelectionRegion; x = Math.Max(0, imagePoint.X - _dragOriginOffset.X); if (x + selectionRegion.Width >= viewSize.Width) { x = viewSize.Width - (int)selectionRegion.Width; } y = Math.Max(0, imagePoint.Y - _dragOriginOffset.Y); if (y + selectionRegion.Height >= viewSize.Height) { y = viewSize.Height - (int)selectionRegion.Height; } this.SelectionRegion = new RectangleF(x, y, selectionRegion.Width, selectionRegion.Height); } } private void ProcessSelectionResize(Point cursorPosition) { if (_isResizing) { Point imagePosition; float left; float top; float right; float bottom; bool resizingTopEdge; bool resizingBottomEdge; bool resizingLeftEdge; bool resizingRightEdge; RectangleF selectionRegion; Size viewSize; imagePosition = this.PointToImage(cursorPosition); viewSize = this.ViewSize; // get the current selection selectionRegion = this.SelectionRegion; left = selectionRegion.Left; top = selectionRegion.Top; right = selectionRegion.Right; bottom = selectionRegion.Bottom; // decide which edges we're resizing resizingTopEdge = _resizeAnchor >= DragHandleAnchor.TopLeft && _resizeAnchor <= DragHandleAnchor.TopRight; resizingBottomEdge = _resizeAnchor >= DragHandleAnchor.BottomLeft && _resizeAnchor <= DragHandleAnchor.BottomRight; resizingLeftEdge = _resizeAnchor == DragHandleAnchor.TopLeft || _resizeAnchor == DragHandleAnchor.MiddleLeft || _resizeAnchor == DragHandleAnchor.BottomLeft; resizingRightEdge = _resizeAnchor == DragHandleAnchor.TopRight || _resizeAnchor == DragHandleAnchor.MiddleRight || _resizeAnchor == DragHandleAnchor.BottomRight; // and resize! if (resizingTopEdge) { top = imagePosition.Y > 0 ? imagePosition.Y : 0; if (bottom - top < this.MinimumSelectionSize.Height) { top = bottom - this.MinimumSelectionSize.Height; } else if (this.MaximumSelectionSize.Height > 0 && bottom - top > this.MaximumSelectionSize.Height) { top = bottom - this.MaximumSelectionSize.Height; } } else if (resizingBottomEdge) { bottom = imagePosition.Y < viewSize.Height ? imagePosition.Y : viewSize.Height; if (bottom - top < this.MinimumSelectionSize.Height) { bottom = top + this.MinimumSelectionSize.Height; } else if (this.MaximumSelectionSize.Height > 0 && bottom - top > this.MaximumSelectionSize.Height) { bottom = top + this.MaximumSelectionSize.Height; } } if (resizingLeftEdge) { left = imagePosition.X > 0 ? imagePosition.X : 0; if (right - left < this.MinimumSelectionSize.Width) { left = right - this.MinimumSelectionSize.Width; } else if (this.MaximumSelectionSize.Width > 0 && right - left > this.MaximumSelectionSize.Width) { left = right - this.MaximumSelectionSize.Width; } } else if (resizingRightEdge) { right = imagePosition.X < viewSize.Width ? imagePosition.X : viewSize.Width; if (right - left < this.MinimumSelectionSize.Width) { right = left + this.MinimumSelectionSize.Width; } else if (this.MaximumSelectionSize.Width > 0 && right - left > this.MaximumSelectionSize.Width) { right = left + this.MaximumSelectionSize.Width; } } this.SelectionRegion = new RectangleF(left, top, right - left, bottom - top); } } private void ResetDrag() { _isResizing = false; _isMoving = false; _dragOrigin = Point.Empty; _dragOriginOffset = Point.Empty; } private void StartResize(DragHandleAnchor anchor) { CancelEventArgs e; if (_isMoving || _isResizing) { throw new InvalidOperationException("A move or resize action is currently being performed."); } e = new CancelEventArgs(); this.OnSelectionResizing(e); if (!e.Cancel) { _resizeAnchor = anchor; _previousSelectionRegion = this.SelectionRegion; _isResizing = true; } } #endregion } }