using Microsoft.VisualBasic.CompilerServices; using Pilz.Drawing; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Windows.Forms; namespace Pilz.UI; public class PaintingControl : UserControl, IPaintingObjectContainer { private PaintingObject curObjMouseDown = null; private Color bgColor = Color.White; private Point startMousePos = default; private Point lastMousePos = default; private int lastHashCode = 0; private Point calcOffset_MouseOnTab = Point.Empty; private bool calcOffset_IsActive = false; private PointF calcOffset_LastOffset = PointF.Empty; private new Color ForeColor { get; set; } private new Font Font { get; set; } private new string Text { get; set; } public PointF Offset { get; set; } = PointF.Empty; public PaintingObjectList PaintingObjects { get; private set; } public bool VisibleForMouseEvents { get; set; } = true; public bool AutoAreaSelection { get; set; } = true; public bool AutoSingleSelection { get; set; } = true; public bool AutoMultiselection { get; set; } = true; public bool AutoRemoveSelection { get; set; } = true; public DashStyle AreaSelectionDashStyle { get; set; } = DashStyle.DashDot; public Color AreaSelectionColor { get; set; } = Color.CornflowerBlue; public bool AutoMoveObjects { get; set; } = true; private bool _IsAreaSelecting = false; public bool IsMovingObjects { get; private set; } = false; public bool GridEnabled { get; set; } = true; public bool GridVisible { get; set; } = false; public Size GridChunkSize { get; set; } = new Size(20, 20); public Color GridColor { get; set; } = Color.LightGray; public DelegateDrawPaintingControlGridMethode DrawGridMethode { get; set; } = DefaultDrawMethodes.DrawGrid; public DelegateDrawPaintingControlAreaSelectionMethode DrawAreaSelectionMethode { get; set; } = DefaultDrawMethodes.DrawAreaSelection; private SizeF _ZoomFactor = new SizeF(1f, 1f); private int _stopDrawing = -1; private Image bufferedImg = null; private bool pressedShift = false; private bool pressedControl = false; private bool pressedAlt = false; private Dictionary savedPos = new Dictionary(); public event SelectionChangedEventHandler SelectionChanged; public delegate void SelectionChangedEventHandler(object sender, PaintingObjectEventArgs e); public event PaintingObjectAddedEventHandler PaintingObjectAdded; public delegate void PaintingObjectAddedEventHandler(object sender, PaintingObjectEventArgs e); public event PaintingObjectRemovedEventHandler PaintingObjectRemoved; public delegate void PaintingObjectRemovedEventHandler(object sender, PaintingObjectEventArgs e); public event AfterScrollingDoneEventHandler AfterScrollingDone; public delegate void AfterScrollingDoneEventHandler(object sender, EventArgs e); public event ZoomFactorChangedEventHandler ZoomFactorChanged; public delegate void ZoomFactorChangedEventHandler(object sender, EventArgs e); public PaintingObject[] SelectedObjects { get { var objs = new List(); foreach (PaintingObject obj in PaintingObjects) { if (obj.Selected) objs.Add(obj); } return objs.ToArray(); } } public bool IsLayoutSuspended { get { return Conversions.ToInteger(GetType().GetField("layoutSuspendCount", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(this)) != 0; } } public bool StopDrawing { get { return _stopDrawing > -1; } } public override Color BackColor { get { return bgColor; } set { bgColor = value; base.BackColor = value; // If value <> Color.Transparent Then // MyBase.BackColor = value // End If } } public bool IsAreaSelecting { get { return _IsAreaSelecting && startMousePos != lastMousePos; } } public SizeF ZoomFactor { get { return _ZoomFactor; } set { if (_ZoomFactor != value) { _ZoomFactor = value; ResetAllBufferedImages(); ZoomFactorChanged?.Invoke(this, new EventArgs()); } } } public PaintingControl() { PaintingObjects = new(this); PaintingObjectResizing.CheckEnabled += PaintingObjectResizing_CheckEnabled; DoubleBuffered = true; KeyDown += CheckKeyDown; KeyUp += CheckKeyDown; MouseClick += CheckMouseClick; MouseDown += CheckMouseDown; MouseUp += CheckMouseUp; MouseMove += CheckMouseMove; PaintingObjectAdded += PaintingControl_PaintingObjectAdded; PaintingObjectRemoved += PaintingControl_PaintingObjectAdded; MouseWheel += CheckMouseWheel; } ~PaintingControl() { PaintingObjectResizing.CheckEnabled -= PaintingObjectResizing_CheckEnabled; } private void PaintingObjectResizing_CheckEnabled(PaintingObjectResizing sender, ref bool enabled) { if (PaintingObjects.Contains(sender.PaintingObject) && pressedAlt) enabled = false; } private void ResetAllBufferedImages() { foreach (PaintingObject ob in PaintingObjects) ob.ResetImageBuffer(); Refresh(); } protected void CheckKeyDown(object sender, KeyEventArgs e) { pressedShift = e.Shift; pressedControl = e.Control; pressedAlt = e.Alt; } internal RectangleF AreaSelectionRectangle { get { return HelpfulDrawingFunctions.GetRectangle(startMousePos, lastMousePos); } } protected void CheckMouseClick(object sender, MouseEventArgs e) { foreach (PaintingObject obj in GetObjects(new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)))) { if (!obj.MouseTransparency) obj.RaiseMouseClick(GetMouseEventArgs(e, obj)); } } protected void CheckMouseDown(object sender, MouseEventArgs e) { lastMousePos = new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)); curObjMouseDown = GetObjects(lastMousePos).Where(n => !n.MouseTransparency).LastOrDefault(); curObjMouseDown?.RaiseMouseDown(GetMouseEventArgs(e, curObjMouseDown)); if (curObjMouseDown is null || !curObjMouseDown.Selected || pressedControl) { bool hasMovedObjects = false; if (IsMovingObjects) { foreach (PaintingObject obj in GetSelectedObjects()) { if (HelpfulDrawingFunctions.IsPointInRectangle(lastMousePos, obj.Rectangle)) { hasMovedObjects = true; break; } } } if (!hasMovedObjects && !_IsAreaSelecting) { var selChanged = new List(); if (AutoRemoveSelection && !pressedControl) { foreach (PaintingObject obj in PaintingObjects) { if (obj.Selected) { obj.SelectedDirect = false; if (!selChanged.Contains(obj)) selChanged.Add(obj); } } } if (AutoSingleSelection && curObjMouseDown is not null) { var objtosel = curObjMouseDown; if (objtosel.EnableSelection) { objtosel.SelectedDirect = !objtosel.Selected; if (!selChanged.Contains(objtosel)) selChanged.Add(objtosel); else { selChanged.Remove(objtosel); } } } SelectionChanged?.Invoke(this, new PaintingObjectEventArgs(selChanged.ToArray())); } } if (pressedAlt) { calcOffset_MouseOnTab = new Point(e.X, e.Y); calcOffset_LastOffset = Offset; calcOffset_IsActive = true; Cursor = Cursors.Arrow; } else { switch (e.Button) { case MouseButtons.Left: { savedPos.Clear(); if (AutoMoveObjects) SaveObjectPositions(e, GetSelectedObjects()); if (savedPos.Count > 0) IsMovingObjects = true; else if (AutoAreaSelection) { startMousePos = new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)); lastMousePos = startMousePos; // New Point(e.X - Offset.X, e.Y - Offset.Y) _IsAreaSelecting = true; } break; } } } } public void RaiseSelectionChanged() { SelectionChanged?.Invoke(this, new PaintingObjectEventArgs(SelectedObjects)); } protected void CheckMouseUp(object sender, MouseEventArgs e) { if (_IsAreaSelecting) _IsAreaSelecting = false; if (IsMovingObjects) { IsMovingObjects = false; foreach (PaintingObject obj in GetSelectedObjects()) obj.RaiseMoved(new EventArgs()); AutoArrangeToGrid(); } if (curObjMouseDown is not null) { if (!curObjMouseDown.MouseTransparency) curObjMouseDown.RaiseMouseUp(GetMouseEventArgs(e, curObjMouseDown)); curObjMouseDown = null; } if (calcOffset_IsActive) { calcOffset_IsActive = false; Cursor = Cursors.Default; CalcNewOffset(e.Location); AfterScrollingDone?.Invoke(this, new EventArgs()); } } protected void CheckMouseMove(object sender, MouseEventArgs e) { if (_IsAreaSelecting || IsMovingObjects) lastMousePos = new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)); if (_IsAreaSelecting) SelectControlsInArea(); if (IsMovingObjects) UpdateObjectPositions(e); foreach (PaintingObject obj in GetObjects(new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)))) { if (!obj.MouseTransparency) obj.RaiseMouseMove(GetMouseEventArgs(e, obj)); } var topObj = GetObject(new Point((int)Math.Round(e.X + Offset.X), (int)Math.Round(e.Y + Offset.Y)), true); if (topObj is not null) Cursor = topObj.Cursor; else if (calcOffset_IsActive) { Cursor = Cursors.Arrow; } else { Cursor = Cursors.Default; } if (calcOffset_IsActive) { if (pressedAlt) CalcNewOffset(e.Location); else { calcOffset_IsActive = false; } } Refresh(); } private void CalcNewOffset(Point newMousePos) { Offset = new PointF(calcOffset_LastOffset.X - (newMousePos.X - calcOffset_MouseOnTab.X), calcOffset_LastOffset.Y - (newMousePos.Y - calcOffset_MouseOnTab.Y)); if (Offset.X < 0f) Offset = new PointF(0f, Offset.Y); if (Offset.Y < 0f) Offset = new PointF(Offset.X, 0f); } private PaintingObject[] GetSelectedObjects() { var objs = new List(); foreach (PaintingObject obj in PaintingObjects) { if (obj.Selected) objs.Add(obj); } return objs.ToArray(); } private void SaveObjectPositions(MouseEventArgs e, IList objs) { foreach (PaintingObject obj in objs) { if (!obj.HardcodedLocation && !savedPos.ContainsKey(obj)) { savedPos.Add(obj, new PointF(e.X - obj.Location.X + Offset.X, e.Y - obj.Location.Y + Offset.Y)); SaveObjectPositions(e, obj.PinnedObjects); } } } private void UpdateObjectPositions(MouseEventArgs e) { UpdateObjectPositions(e, GetSelectedObjects()); } private void UpdateObjectPositions(MouseEventArgs e, IList objs, List movedObjs = null) { if (IsResizingObjs(objs)) return; if (movedObjs is null) movedObjs = new List(); SuspendDrawing(); foreach (PaintingObject obj in objs) { var sp = savedPos[obj]; if (!movedObjs.Contains(obj)) { if (UpdateObjectPosition(e, obj, sp)) movedObjs.Add(obj); } if (obj.PinnedObjects.Count > 0) { UpdateObjectPositions(e, obj.PinnedObjects, movedObjs); movedObjs.AddRange(obj.PinnedObjects.ToArray()); } } ResumeDrawing(false); } private bool UpdateObjectPosition(MouseEventArgs e, PaintingObject obj, PointF sp) { bool moved = false; var cancel = new CancelEventArgs(false); obj.RaiseMovingBeforePositionUpdated(cancel); if (!cancel.Cancel) { obj.Location = new Point((int)Math.Round(e.X - sp.X + Offset.X), (int)Math.Round(e.Y - sp.Y + Offset.Y)); obj.RaiseMoving(new EventArgs()); moved = true; } return moved; } private bool IsResizingObjs(IList objs) { foreach (PaintingObject obj in objs) { if (obj.IsResizing) return true; } return false; } private MouseEventArgs GetMouseEventArgs(MouseEventArgs e, PaintingObject obj) { return new MouseEventArgs(e.Button, e.Clicks, (int)Math.Round(e.X - obj.X + Offset.X), (int)Math.Round(e.Y - obj.Y + Offset.Y), e.Delta); } public PaintingObject GetObject(PointF p, bool UseExtRect = false) { PaintingObject val = null; for (int i = PaintingObjects.Count - 1; i >= 0; i -= 1) { var obj = PaintingObjects[i]; if (val is null) { if (UseExtRect) { if (HelpfulDrawingFunctions.IsPointInRectangle(p, obj.RectangleExtended)) val = obj; } else if (HelpfulDrawingFunctions.IsPointInRectangle(p, obj.Rectangle)) { val = obj; } } } return val; } public PaintingObject[] GetObjects(Point p) { var objs = new List(); foreach (PaintingObject obj in PaintingObjects) { if (HelpfulDrawingFunctions.IsPointInRectangle(p, obj.RectangleExtended)) objs.Add(obj); } return objs.ToArray(); } public PaintingObject[] GetObjects(Point startPoint, Point endPoint) { return GetObjects(new Rectangle(startPoint, (Size)(endPoint - (Size)startPoint))); } public PaintingObject[] GetObjects(Rectangle rect) { var objs = new List(); foreach (PaintingObject obj in PaintingObjects) { var objRect = obj.Rectangle; if (HelpfulDrawingFunctions.IsPointInRectangle(objRect.Location, rect) || HelpfulDrawingFunctions.IsPointInRectangle(objRect.Location + objRect.Size, rect) || HelpfulDrawingFunctions.IsPointInRectangle(new PointF(objRect.Left, objRect.Bottom), rect) || HelpfulDrawingFunctions.IsPointInRectangle(new PointF(objRect.Right, objRect.Top), rect)) objs.Add(obj); } return objs.ToArray(); } protected override CreateParams CreateParams { get { var cp = base.CreateParams; // If EnableRealTransparency Then // cp.ExStyle = cp.ExStyle Or &H20 'WS_EX_TRANSPARENT // End If return cp; } } /// /// Sorg dafür, dass Events durch dieses Control durchdringen zum Parnet-Control. /// /// protected override void WndProc(ref Message m) { const int WM_NCHITTEST = 0x84; const int HTTRANSPARENT = -1; if (!VisibleForMouseEvents && m.Msg == WM_NCHITTEST) m.Result = HTTRANSPARENT; else { base.WndProc(ref m); } } protected override void OnPaintBackground(PaintEventArgs e) { // Stop Drawing directly to the parent SuspendLayout(); // Draw Background // If Not EnableRealTransparency Then base.OnPaintBackground(e); // End If } protected override void OnPaint(PaintEventArgs e) { // Draw PaintingObjects stuff if (StopDrawing) e.Graphics.DrawImage(bufferedImg, Point.Empty); else { { var withBlock = e.Graphics; withBlock.SmoothingMode = SmoothingMode.HighQuality; withBlock.PixelOffsetMode = PixelOffsetMode.HighQuality; withBlock.PageUnit = GraphicsUnit.Pixel; withBlock.InterpolationMode = InterpolationMode.HighQualityBicubic; } if (GridVisible) DrawGridMethode?.Invoke(e, this, Offset); var baserect = new RectangleF(Offset, Size); foreach (PaintingObject obj in PaintingObjects) { if (obj.Visible && HelpfulDrawingFunctions.OverlapsTwoRectangles(obj.Rectangle, baserect)) obj.Draw(e, Offset); } if (_IsAreaSelecting) DrawAreaSelectionMethode?.Invoke(e, this, new PointF(startMousePos.X - Offset.X, startMousePos.Y - Offset.Y), new PointF(lastMousePos.X - Offset.X, lastMousePos.Y - Offset.Y)); } // Do default Drawing Methode base.OnPaint(e); // Start Drawing directly to the Form ResumeLayout(false); } public new Graphics CreateGraphics() { return base.CreateGraphics(); } public void PaintFullView(Graphics g) { foreach (PaintingObject obj in PaintingObjects) { if (obj.Visible) obj.Draw(g, PointF.Empty); } } private SizeF CalcTextSize(PaintingObject obj) { return CalcTextSize(obj, Parent.CreateGraphics()); } private SizeF CalcTextSize(PaintingObject obj, Graphics g) { return g.MeasureString(obj.Text, obj.TextFont, (int)Math.Round(obj.Width)); } private void SelectControlsInArea() { var rect = HelpfulDrawingFunctions.GetRectangle(startMousePos, lastMousePos); foreach (PaintingObject obj in PaintingObjects) obj.Selected = startMousePos.X >= lastMousePos.X ? HelpfulDrawingFunctions.OverlapsTwoRectangles(obj.Rectangle, rect) : HelpfulDrawingFunctions.RectangleContainsRectangle(rect, obj.Rectangle); } public void ArrangeToGrid(PaintingObject obj, bool snapPinnedObjects) { if (snapPinnedObjects || !IsPinnedObject(obj)) { var zoomedGridChunkSize = new SizeF(GridChunkSize.Width * ZoomFactor.Width, GridChunkSize.Height * ZoomFactor.Height); int modTop = (int)Math.Round(obj.Y % zoomedGridChunkSize.Height); int modLeft = (int)Math.Round(obj.X % zoomedGridChunkSize.Width); int halfHeight = (int)Math.Round(zoomedGridChunkSize.Height / 2f); int halfWidth = (int)Math.Round(zoomedGridChunkSize.Width / 2f); void zoomLocation(PaintingObject obj2) { if (modTop > halfHeight) obj2.Y += zoomedGridChunkSize.Height - modTop; else { obj2.Y -= modTop; } if (modLeft > halfWidth) obj2.X += zoomedGridChunkSize.Width - modLeft; else { obj2.X -= modLeft; } }; zoomLocation(obj); foreach (PaintingObject pinned in obj.PinnedObjects) zoomLocation(pinned); int modH = (int)Math.Round(obj.Height % zoomedGridChunkSize.Height); int modW = (int)Math.Round(obj.Width % zoomedGridChunkSize.Width); void zoomSize(PaintingObject obj2) { if (obj2.EnableResize && !obj2.HardcodedSize) { if (modH > halfHeight) obj2.Height += zoomedGridChunkSize.Height - modH; else { obj2.Height -= modH; } if (modW > halfWidth) obj2.Width += zoomedGridChunkSize.Width - modW; else { obj2.Width -= modW; } } }; zoomSize(obj); foreach (PaintingObject pinned in obj.PinnedObjects) zoomSize(pinned); } } public bool IsPinnedObject(PaintingObject o) { foreach (PaintingObject obj in PaintingObjects) { if (obj.PinnedObjects.Contains(o)) return true; } return false; } public void AutoArrangeToGrid() { if (GridEnabled) { foreach (PaintingObject obj in GetSelectedObjects()) { if (obj.AutoAlignToGrid) ArrangeToGrid(obj, false); } if (!StopDrawing) Refresh(); } } public SizeF GetFullSize() { return GetFullSize(PaintingObjects); } public static SizeF GetFullSize(IEnumerable objects) { float curX = 0f; float curY = 0f; foreach (PaintingObject po in objects) { float myX = po.X + po.Width; if (curX < myX) curX = myX; float myY = po.Y + po.Height; if (curY < myY) curY = myY; } return new SizeF(curX + 20f, curY + 20f); } internal void RaisePaintingObjectAdded(PaintingObjectEventArgs args) { PaintingObjectAdded?.Invoke(this, args); } internal void RaisePaintingObjectRemoved(PaintingObjectEventArgs args) { PaintingObjectRemoved?.Invoke(this, args); } private void PaintingControl_PaintingObjectAdded(object sender, PaintingObjectEventArgs e) { // CalculateScrollValues() } private void CheckMouseWheel(object sender, MouseEventArgs e) { if (pressedAlt) { float val = (float)(e.Delta / 120d / 10d); ZoomFactor = new SizeF((float)Math.Max((double)(ZoomFactor.Width + val), 0.25d), (float)Math.Max((double)(ZoomFactor.Height + val), 0.25d)); Refresh(); } else { // ... } } public void SuspendDrawing() { if (_stopDrawing < 0) // bufferedImg = New Bitmap(Width, Height) // DrawToBitmap(bufferedImg, New Rectangle(0, 0, bufferedImg.Width, bufferedImg.Height)) Utils.DrawingControl.SuspendDrawing(this); _stopDrawing += 1; } public void ResumeDrawing() { ResumeDrawing(true); } public void ResumeDrawing(bool executeRefresh) { if (_stopDrawing >= 0) _stopDrawing -= 1; if (_stopDrawing == -1) // bufferedImg.Dispose() // bufferedImg = Nothing // If executeRefresh Then Refresh() Utils.DrawingControl.ResumeDrawing(this, executeRefresh); } }