/* * This file is part of the "GKMap". * GKMap project borrowed from GMap.NET (by radioman). * * Copyright (C) 2009-2018 by radioman (email@radioman.lt). * This program is licensed under the FLAT EARTH License. */ //#define DEBUG_TILE_COORDS //#define DEBUG_CENTER //#define DEBUG_RENDER using System; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using System.Windows.Forms; using GKMap.MapObjects; using GKMap.MapProviders; namespace GKMap.WinForms { public delegate void MarkerClick(MapMarker item, MouseEventArgs e); public delegate void MarkerDoubleClick(MapMarker item, MouseEventArgs e); public delegate void PolygonClick(MapPolygon item, MouseEventArgs e); public delegate void PolygonDoubleClick(MapPolygon item, MouseEventArgs e); public delegate void RouteClick(MapRoute item, MouseEventArgs e); public delegate void RouteDoubleClick(MapRoute item, MouseEventArgs e); /// /// GKMap control for Windows Forms /// public class GMapControl : UserControl, IMapControl { public static readonly bool IsDesignerHosted = LicenseManager.UsageMode == LicenseUsageMode.Designtime; private Bitmap fBackBuffer; private MapCore fCore; private Cursor fCursorBefore = Cursors.Default; private bool fForceDoubleBuffer; private Graphics fGxOff; private bool fIsMouseOverMarker; private bool fIsMouseOverPolygon; private bool fIsMouseOverRoute; private int fOverObjectCount; private readonly StringFormat BottomFormat = new StringFormat(); private readonly StringFormat CenterFormat = new StringFormat(); private readonly Font CopyrightFont = new Font(FontFamily.GenericSansSerif, 7, FontStyle.Regular); private readonly Color EmptyMapBackground = Color.WhiteSmoke; private readonly Pen EmptyTileBorders = new Pen(Brushes.White, 1); private readonly Brush EmptyTileBrush = new SolidBrush(Color.Navy); private readonly string EmptyTileText = "We are sorry, but we don't\nhave imagery at this zoom\nlevel for this region."; private readonly Font MissingDataFont = new Font(FontFamily.GenericSansSerif, 11, FontStyle.Bold); private readonly Font ScaleFont = new Font(FontFamily.GenericSansSerif, 5, FontStyle.Italic); private readonly ImageAttributes TileFlipXYAttributes = new ImageAttributes(); #if DEBUG_CENTER || DEBUG_RENDER private readonly Pen CenterPen = new Pen(Brushes.Red, 1); private readonly Pen ScalePen = new Pen(Brushes.Blue, 1); #endif #region Properties /// /// location of cache /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] public string CacheLocation { get { #if !DESIGN return GMaps.CacheLocation; #else return string.Empty; #endif } set { #if !DESIGN GMaps.CacheLocation = value; #endif } } MapCore IMapControl.Core { get { return fCore; } } /// /// stops immediate marker/route/polygon invalidation; /// call Refresh to perform single refresh and reset invalidation state /// public bool HoldInvalidation { get; set; } /// /// reverses MouseWheel zooming direction /// public bool InvertedMouseWheelZooming { get { return fCore.InvertedMouseWheelZooming; } set { fCore.InvertedMouseWheelZooming = value; } } /// /// is mouse over marker /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] public bool IsMouseOverMarker { get { return fIsMouseOverMarker; } set { fIsMouseOverMarker = value; fOverObjectCount += value ? 1 : -1; } } /// /// is mouse over polygon /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] public bool IsMouseOverPolygon { get { return fIsMouseOverPolygon; } set { fIsMouseOverPolygon = value; fOverObjectCount += value ? 1 : -1; } } /// /// is mouse over route /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] public bool IsMouseOverRoute { get { return fIsMouseOverRoute; } set { fIsMouseOverRoute = value; fOverObjectCount += value ? 1 : -1; } } [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] public GMapProvider MapProvider { get { return fCore.Provider; } set { if (fCore.Provider == null || !fCore.Provider.Equals(value)) { Debug.WriteLine("MapType: " + fCore.Provider.Name + " -> " + value.Name); RectLatLng viewarea = fCore.ViewArea; fCore.Provider = value; if (fCore.IsStarted) { if (fCore.ZoomToArea) { // restore zoom rect as close as possible if (viewarea != RectLatLng.Empty && viewarea != fCore.ViewArea) { int bestZoom = fCore.GetMaxZoomToFitRect(viewarea); if (bestZoom > 0 && Zoom != bestZoom) { Zoom = bestZoom; } } } else { fCore.ForceUpdateOverlays(); } } } } } /// /// max zoom /// [Category("GKMap")] [Description("maximum zoom level of map")] public int MaxZoom { get { return fCore.MaxZoom; } set { fCore.MaxZoom = value; } } /// /// min zoom /// [Category("GKMap")] [Description("minimum zoom level of map")] public int MinZoom { get { return fCore.MinZoom; } set { fCore.MinZoom = value; } } /// /// list of overlays, should be thread safe /// public ObservableCollectionThreadSafe Overlays { get; private set; } /// /// current map center position /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] public PointLatLng Position { get { return fCore.Position; } set { fCore.Position = value; if (fCore.IsStarted) { fCore.ForceUpdateOverlays(); } } } [Category("GKMap"), DefaultValue(0)] public int Zoom { get { return fCore.Zoom; } set { if (fCore.Zoom != value) { Debug.WriteLine("ZoomPropertyChanged: " + fCore.Zoom + " -> " + value); if (value > MaxZoom) { fCore.Zoom = MaxZoom; } else if (value < MinZoom) { fCore.Zoom = MinZoom; } else { fCore.Zoom = value; } if (fCore.IsStarted && !fCore.IsDragging) { fCore.ForceUpdateOverlays(); } } } } #endregion #region Events /// /// occurs when current position is changed /// public event PositionChanged OnPositionChanged { add { fCore.OnCurrentPositionChanged += value; } remove { fCore.OnCurrentPositionChanged -= value; } } /// /// occurs when tile set load is complete /// public event TileLoadComplete OnTileLoadComplete { add { fCore.OnTileLoadComplete += value; } remove { fCore.OnTileLoadComplete -= value; } } /// /// occurs when tile set is starting to load /// public event TileLoadStart OnTileLoadStart { add { fCore.OnTileLoadStart += value; } remove { fCore.OnTileLoadStart -= value; } } /// /// occurs on map drag /// public event MapDrag OnMapDrag { add { fCore.OnMapDrag += value; } remove { fCore.OnMapDrag -= value; } } /// /// occurs on map zoom changed /// public event MapZoomChanged OnMapZoomChanged { add { fCore.OnMapZoomChanged += value; } remove { fCore.OnMapZoomChanged -= value; } } /// /// occurs on map type changed /// public event MapTypeChanged OnMapTypeChanged { add { fCore.OnMapTypeChanged += value; } remove { fCore.OnMapTypeChanged -= value; } } /// /// occurs when clicked on marker /// public event MarkerClick OnMarkerClick; /// /// occurs when double clicked on marker /// public event MarkerDoubleClick OnMarkerDoubleClick; /// /// occurs on mouse enters marker area /// public event MarkerEnter OnMarkerEnter { add { fCore.OnMarkerEnter += value; } remove { fCore.OnMarkerEnter -= value; } } /// /// occurs on mouse leaves marker area /// public event MarkerLeave OnMarkerLeave { add { fCore.OnMarkerLeave += value; } remove { fCore.OnMarkerLeave -= value; } } /// /// occurs when clicked on polygon /// public event PolygonClick OnPolygonClick; /// /// occurs when double clicked on polygon /// public event PolygonDoubleClick OnPolygonDoubleClick; /// /// occurs on mouse enters Polygon area /// public event PolygonEnter OnPolygonEnter { add { fCore.OnPolygonEnter += value; } remove { fCore.OnPolygonEnter -= value; } } /// /// occurs on mouse leaves Polygon area /// public event PolygonLeave OnPolygonLeave { add { fCore.OnPolygonLeave += value; } remove { fCore.OnPolygonLeave -= value; } } /// /// occurs when clicked on route /// public event RouteClick OnRouteClick; /// /// occurs when double clicked on route /// public event RouteDoubleClick OnRouteDoubleClick; /// /// occurs on mouse enters route area /// public event RouteEnter OnRouteEnter { add { fCore.OnRouteEnter += value; } remove { fCore.OnRouteEnter -= value; } } /// /// occurs on mouse leaves route area /// public event RouteLeave OnRouteLeave { add { fCore.OnRouteLeave += value; } remove { fCore.OnRouteLeave -= value; } } #endregion static GMapControl() { if (!IsDesignerHosted) { GMaps.Initialize(GMapImageProxy.Instance); } } #if !DESIGN /// /// constructor /// public GMapControl() { fCore = new MapCore(this); Overlays = new ObservableCollectionThreadSafe(); InvertedMouseWheelZooming = false; MaxZoom = 17; MinZoom = 2; Zoom = 0; if (!IsDesignerHosted) { SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.Opaque, true); ResizeRedraw = true; TileFlipXYAttributes.SetWrapMode(WrapMode.TileFlipXY); CenterFormat.Alignment = StringAlignment.Center; CenterFormat.LineAlignment = StringAlignment.Center; BottomFormat.Alignment = StringAlignment.Center; BottomFormat.LineAlignment = StringAlignment.Far; Overlays.CollectionChanged += Overlays_CollectionChanged; } } #endif protected override void Dispose(bool disposing) { if (disposing) { fCore.OnMapClose(); Overlays.CollectionChanged -= Overlays_CollectionChanged; foreach (var o in Overlays) { o.Dispose(); } Overlays.Clear(); #if DEBUG_CENTER CenterPen.Dispose(); ScalePen.Dispose(); #endif ScaleFont.Dispose(); CenterFormat.Dispose(); BottomFormat.Dispose(); CopyrightFont.Dispose(); EmptyTileBorders.Dispose(); EmptyTileBrush.Dispose(); ClearBackBuffer(); } base.Dispose(disposing); } /// /// Call it to empty tile cache & reload tiles /// public void ReloadMap() { fCore.ReloadMap(); } /// /// set current position using keywords /// /// /// true if successful public GeocoderStatusCode SetPositionByKeywords(string keys) { return fCore.SetPositionByKeywords(keys); } /// /// gets world coordinate from local control coordinate /// /// /// /// public PointLatLng FromLocalToLatLng(int x, int y) { return fCore.FromLocalToLatLng(x, y); } /// /// gets local coordinate from world coordinate /// /// /// public GPoint FromLatLngToLocal(PointLatLng point) { return fCore.FromLatLngToLocal(point); } /// /// call this to stop HoldInvalidation and perform single forced instant refresh /// public override void Refresh() { HoldInvalidation = false; lock (fCore.InvalidationLock) { fCore.LastInvalidation = DateTime.Now; } base.Refresh(); } #if !DESIGN /// /// enqueue built-in thread safe invalidation /// public new void Invalidate() { if (fCore.RefreshEvent != null && !HoldInvalidation) { fCore.RefreshEvent.Set(); } } #endif /// /// sets to max zoom to fit all markers and centers them in map /// /// overlay id or null to check all /// public bool ZoomAndCenterMarkers(string overlayId) { return fCore.ZoomAndCenterMarkers(overlayId); } /// /// zooms and centers all route /// /// overlay id or null to check all /// public bool ZoomAndCenterRoutes(string overlayId) { return fCore.ZoomAndCenterRoutes(overlayId); } /// /// zooms and centers route /// /// /// public bool ZoomAndCenterFigure(MapFigure figure) { return fCore.ZoomAndCenterFigure(figure); } /// /// gets image of the current view /// /// public Image ToImage() { Image ret; bool r = fForceDoubleBuffer; try { UpdateBackBuffer(); if (!r) { fForceDoubleBuffer = true; } Refresh(); Application.DoEvents(); using (MemoryStream ms = new MemoryStream()) { using (var frame = (fBackBuffer.Clone() as Bitmap)) { frame.Save(ms, ImageFormat.Png); } ret = Image.FromStream(ms); } } catch (Exception) { throw; } finally { if (!r) { fForceDoubleBuffer = false; ClearBackBuffer(); } } return ret; } /// /// offset position in pixels /// /// /// public void Offset(int x, int y) { if (IsHandleCreated) { fCore.DragOffset(new GPoint(x, y)); } } #region UserControl Events protected override void OnLoad(EventArgs e) { base.OnLoad(e); if (!IsDesignerHosted) { fCore.ResetZoomToFitRect(); fCore.OnMapOpen().ProgressChanged += invalidatorEngage; fCore.ForceUpdateOverlays(); } } protected override void OnCreateControl() { base.OnCreateControl(); if (!IsDesignerHosted) { var f = ParentForm; if (f != null) { while (f.ParentForm != null) { f = f.ParentForm; } f.FormClosing += ParentForm_FormClosing; } } } #if !DESIGN #if DEBUG private int fCounter; private readonly Font fDebugFont = new Font(FontFamily.GenericSansSerif, 12, FontStyle.Regular); private DateTime fStart; private DateTime fEnd; private int fDelta; #endif protected override void OnPaint(PaintEventArgs e) { #if DEBUG fStart = DateTime.Now; #endif if (fForceDoubleBuffer) { if (fGxOff != null) { DrawGraphics(fGxOff); e.Graphics.DrawImage(fBackBuffer, 0, 0); } } else { DrawGraphics(e.Graphics); } base.OnPaint(e); #if DEBUG fEnd = DateTime.Now; fDelta = (int)(fEnd - fStart).TotalMilliseconds; #endif } private void DrawGraphics(Graphics g) { // render white background g.Clear(EmptyMapBackground); g.TranslateTransform(fCore.RenderOffset.X, fCore.RenderOffset.Y); fCore.DrawMap(g); OnPaintOverlays(g); } #endif /// /// override, to render something more /// /// protected virtual void OnPaintOverlays(Graphics g) { g.SmoothingMode = SmoothingMode.HighQuality; foreach (GMapOverlay o in Overlays) { if (o.IsVisible) { o.OnRender(g); } } // center in virtual space... #if DEBUG_CENTER g.DrawLine(ScalePen, -20, 0, 20, 0); g.DrawLine(ScalePen, 0, -20, 0, 20); g.DrawString("debug: virtual space center", CopyrightFont, Brushes.Blue, 2, CopyrightFont.Height); #endif g.ResetTransform(); // copyright if (!string.IsNullOrEmpty(fCore.Provider.Copyright)) { g.DrawString(fCore.Provider.Copyright, CopyrightFont, Brushes.Navy, 3, Height - CopyrightFont.Height - 5); } #if DEBUG_RENDER // show center g.DrawLine(CenterPen, Width / 2 - 5, Height / 2, Width / 2 + 5, Height / 2); g.DrawLine(CenterPen, Width / 2, Height / 2 - 5, Width / 2, Height / 2 + 5); // debug info g.DrawString(string.Format("{0:0.0}", Zoom) + "z, " + MapProvider + ", refresh: " + fCounter++ + ", render: " + fDelta + "ms", fDebugFont, Brushes.Blue, fDebugFont.Height, fDebugFont.Height + 20); #endif } protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); if (Width == 0 || Height == 0) { Debug.WriteLine("minimized"); return; } if (Width == fCore.Width && Height == fCore.Height) { Debug.WriteLine("maximized"); return; } if (!IsDesignerHosted) { if (fForceDoubleBuffer) { UpdateBackBuffer(); } fCore.OnMapSizeChanged(Width, Height); if (Visible && IsHandleCreated && fCore.IsStarted) { fCore.ForceUpdateOverlays(); } } } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); if (!IsMouseOverMarker && e.Button == MouseButtons.Left) { fCore.MouseDown = new GPoint(e.X, e.Y); Invalidate(); } } protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); if (fCore.IsDragging) { RestoreCursor(); fCore.EndDrag(); } else { if (e.Button == MouseButtons.Left) { fCore.MouseDown = GPoint.Empty; } Invalidate(); } } protected override void OnMouseClick(MouseEventArgs e) { base.OnMouseClick(e); fCore.ProcessMouseClick(e.X, e.Y, e); } protected override void OnMouseDoubleClick(MouseEventArgs e) { base.OnMouseDoubleClick(e); fCore.ProcessMouseDoubleClick(e.X, e.Y, e); } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (!fCore.IsDragging && !fCore.MouseDown.IsEmpty) { // Gets the width and height of a rectangle centered on the point the mouse // button was pressed, within which a drag operation will not begin. var dragSize = SystemInformation.DragSize; if (Math.Abs(e.X - fCore.MouseDown.X) * 2 >= dragSize.Width || Math.Abs(e.Y - fCore.MouseDown.Y) * 2 >= dragSize.Height) { fCore.BeginDrag(fCore.MouseDown); } } if (fCore.IsDragging) { SetCursorDrag(); fCore.MouseCurrent = new GPoint(e.X, e.Y); fCore.Drag(fCore.MouseCurrent); base.Invalidate(); } else { if (fCore.MouseDown.IsEmpty) { fCore.ProcessOverlaysMouseMove(e.X, e.Y); } } } protected override void OnMouseEnter(EventArgs e) { base.OnMouseEnter(e); Focus(); fCore.MouseIn = true; } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); fCore.MouseIn = false; } protected override void OnMouseWheel(MouseEventArgs e) { base.OnMouseWheel(e); fCore.ProcessMouseWheel(e.X, e.Y, e.Delta); } #endregion #region Core callbacks and aux private methods private void ParentForm_FormClosing(object sender, FormClosingEventArgs e) { if (e.CloseReason == CloseReason.WindowsShutDown || e.CloseReason == CloseReason.TaskManagerClosing) { GMaps.Instance.CancelTileCaching(); } } private void UpdateBackBuffer() { ClearBackBuffer(); fBackBuffer = new Bitmap(Width, Height); fGxOff = Graphics.FromImage(fBackBuffer); } private void ClearBackBuffer() { if (fBackBuffer != null) { fBackBuffer.Dispose(); fBackBuffer = null; } if (fGxOff != null) { fGxOff.Dispose(); fGxOff = null; } } private void Overlays_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (GMapOverlay obj in e.NewItems) { if (obj != null) { obj.Control = this; } } if (fCore.IsStarted) { Invalidate(); } } } private void invalidatorEngage(object sender, ProgressChangedEventArgs e) { base.Invalidate(); } void IMapControl.DrawTile(object go, PureImage pureImage, ref bool found) { Graphics g = (Graphics)go; var img = (GMapImage)pureImage; if (img != null && img.Img != null) { if (!found) found = true; if (!img.IsParent) { g.DrawImage(img.Img, fCore.TileRect.X, fCore.TileRect.Y, fCore.TileRect.Width, fCore.TileRect.Height); } else { float wix = ((float)img.Img.Width / img.Ix); float hix = ((float)img.Img.Height / img.Ix); RectangleF srcRect = new RectangleF(img.Xoff * wix, img.Yoff * hix, wix, hix); Rectangle dst = new Rectangle((int)fCore.TileRect.X, (int)fCore.TileRect.Y, (int)fCore.TileRect.Width, (int)fCore.TileRect.Height); g.DrawImage(img.Img, dst, srcRect.X, srcRect.Y, srcRect.Width, srcRect.Height, GraphicsUnit.Pixel, TileFlipXYAttributes); } } } void IMapControl.DrawLowerTile(object go, PureImage pureImage, long Ix, long xoff, long yoff, ref bool found) { Graphics g = (Graphics)go; var img = (GMapImage)pureImage; if (img != null && img.Img != null && !img.IsParent) { if (!found) found = true; RectangleF srcRect = new RectangleF(xoff * (img.Img.Width / Ix), yoff * (img.Img.Height / Ix), (img.Img.Width / Ix), (img.Img.Height / Ix)); Rectangle dst = new Rectangle((int)fCore.TileRect.X, (int)fCore.TileRect.Y, (int)fCore.TileRect.Width, (int)fCore.TileRect.Height); g.DrawImage(img.Img, dst, srcRect.X, srcRect.Y, srcRect.Width, srcRect.Height, GraphicsUnit.Pixel, TileFlipXYAttributes); } } void IMapControl.DrawMissingTile(object go, Exception ex) { Graphics g = (Graphics)go; g.FillRectangle(EmptyTileBrush, new RectangleF(fCore.TileRect.X, fCore.TileRect.Y, fCore.TileRect.Width, fCore.TileRect.Height)); g.DrawString("Exception: " + ex.Message, MissingDataFont, Brushes.Red, new RectangleF(fCore.TileRect.X + 11, fCore.TileRect.Y + 11, fCore.TileRect.Width - 11, fCore.TileRect.Height - 11)); g.DrawString(EmptyTileText, MissingDataFont, Brushes.Blue, new RectangleF(fCore.TileRect.X, fCore.TileRect.Y, fCore.TileRect.Width, fCore.TileRect.Height), CenterFormat); g.DrawRectangle(EmptyTileBorders, (int)fCore.TileRect.X, (int)fCore.TileRect.Y, (int)fCore.TileRect.Width, (int)fCore.TileRect.Height); } void IMapControl.ShowTileGridLines(object go, DrawTile tilePoint) { #if DEBUG_TILE_COORDS Graphics g = (Graphics)go; // show tile grid lines g.DrawRectangle(EmptyTileBorders, (int)fCore.TileRect.X, (int)fCore.TileRect.Y, (int)fCore.TileRect.Width, (int)fCore.TileRect.Height); g.DrawString( (tilePoint.PosXY == fCore.CenterTileXYLocation ? "CENTER: " : "TILE: ") + tilePoint, MissingDataFont, Brushes.DimGray, new RectangleF(fCore.TileRect.X, fCore.TileRect.Y, fCore.TileRect.Width, fCore.TileRect.Height), CenterFormat); #endif } void IMapControl.DoMouseClick(MapObject obj, EventArgs e) { if (obj is MapMarker) { if (OnMarkerClick != null) { OnMarkerClick(obj as MapMarker, (MouseEventArgs)e); } } else if (obj is MapRoute) { if (OnRouteClick != null) { OnRouteClick(obj as MapRoute, (MouseEventArgs)e); } } else if (obj is MapPolygon) { if (OnPolygonClick != null) { OnPolygonClick(obj as MapPolygon, (MouseEventArgs)e); } } } void IMapControl.DoMouseDoubleClick(MapObject obj, EventArgs e) { if (obj is MapMarker) { if (OnMarkerDoubleClick != null) { OnMarkerDoubleClick(obj as MapMarker, (MouseEventArgs)e); } } else if (obj is MapRoute) { if (OnRouteDoubleClick != null) { OnRouteDoubleClick(obj as MapRoute, (MouseEventArgs)e); } } else if (obj is MapPolygon) { if (OnPolygonDoubleClick != null) { OnPolygonDoubleClick(obj as MapPolygon, (MouseEventArgs)e); } } } private void SetCursorHand() { fCursorBefore = Cursor; Cursor = Cursors.Hand; } private void SetCursorDrag() { fCursorBefore = Cursor; Cursor = Cursors.SizeAll; } private void RestoreCursor() { Cursor = Cursors.Arrow; fCursorBefore = null; } void IMapControl.RestoreCursorOnLeave() { if (fOverObjectCount <= 0 && fCursorBefore != null) { fOverObjectCount = 0; RestoreCursor(); } } void IMapControl.SetCursorHandOnEnter() { if (fOverObjectCount <= 0 && Cursor != Cursors.Hand) { fOverObjectCount = 0; SetCursorHand(); } } void IMapControl.SetMousePositionToMapCenter() { if (!fCore.IsRunningOnMono) { Point p = PointToScreen(new Point(Width / 2, Height / 2)); SetCursorPos(p.X, p.Y); } } [DllImport("user32.dll", EntryPoint = "SetCursorPos")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetCursorPos(int x, int y); #endregion } }