Created
April 22, 2020 20:46
-
-
Save overing/8767255061e8cd4d19fa04d6eac6f482 to your computer and use it in GitHub Desktop.
WinForms Task Anime Demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Drawing; | |
using System.Linq; | |
using System.Threading.Tasks; | |
using System.Windows.Forms; | |
namespace WinFormTaskAnimeDemo | |
{ | |
public sealed class MainForm : Form | |
{ | |
Control DraggingControl; | |
Point DraggingStartLocation; | |
Task MoveBackAnime; | |
[STAThread] | |
static void Main() | |
{ | |
Application.SetHighDpiMode(HighDpiMode.SystemAware); | |
Application.EnableVisualStyles(); | |
Application.SetCompatibleTextRenderingDefault(false); | |
Application.Run(new MainForm()); | |
} | |
public MainForm() | |
{ | |
var pictureBox1 = new PictureBox | |
{ | |
Image = Icon.ToBitmap(), | |
Size = new Size(128, 128), | |
SizeMode = PictureBoxSizeMode.StretchImage, | |
BackColor = Color.LightSkyBlue, | |
}; | |
pictureBox1.MouseDown += Control_MouseDown; | |
pictureBox1.MouseMove += Control_MouseMove; | |
pictureBox1.MouseUp += Control_MouseUp; | |
var pictureBox2 = new PictureBox | |
{ | |
Image = Icon.ToBitmap(), | |
Size = new Size(128, 128), | |
SizeMode = PictureBoxSizeMode.StretchImage, | |
BackColor = Color.LightSeaGreen, | |
Location = pictureBox1.Location + pictureBox1.Size, | |
}; | |
pictureBox2.MouseDown += Control_MouseDown; | |
pictureBox2.MouseMove += Control_MouseMove; | |
pictureBox2.MouseUp += Control_MouseUp; | |
var tip = new ToolTip(); | |
tip.SetToolTip(pictureBox1, "Drag me !"); | |
tip.SetToolTip(pictureBox2, "Drag me !"); | |
var panel = new Panel | |
{ | |
Dock = DockStyle.Fill, | |
BackColor = Color.LightGray, | |
}; | |
panel.Controls.Add(pictureBox1); | |
panel.Controls.Add(pictureBox2); | |
panel.Resize += Panel_Resize; | |
SuspendLayout(); | |
Text = "WinForms Task Anime Demo"; | |
Controls.Add(panel); | |
ClientSize = new Size(640, 480); | |
ResumeLayout(false); | |
} | |
void Control_MouseDown(object sender, MouseEventArgs e) | |
{ | |
// 如果有動畫而且還沒跑完就不處理事件 | |
if (MoveBackAnime != null && !MoveBackAnime.IsCompleted) return; | |
// 把觸發事件的控制項跟滑鼠位置記錄下來 | |
DraggingControl = sender as Control; | |
DraggingStartLocation = e.Location; | |
// 把最新被點的移到最前面顯示 | |
DraggingControl.Parent.Controls.SetChildIndex(DraggingControl, 0); | |
} | |
void Control_MouseMove(object sender, MouseEventArgs e) | |
{ | |
// 如果有動畫而且還沒跑完就不處理事件 | |
if (MoveBackAnime != null && !MoveBackAnime.IsCompleted) return; | |
// 如果觸發事件的控制項不是原先觸發 MouseDown 的控制項就不處裡事件 | |
if (sender != DraggingControl) return; | |
// 算出滑鼠的偏移量 | |
var mouseOffset = e.Location.Subtract(DraggingStartLocation); | |
// append 給控制項 | |
DraggingControl.Location = DraggingControl.Location.Add(mouseOffset); | |
} | |
void Control_MouseUp(object sender, MouseEventArgs e) | |
{ | |
// 如果有動畫而且還沒跑完就不處理事件 | |
if (MoveBackAnime != null && !MoveBackAnime.IsCompleted) return; | |
// 如果觸發事件的控制項不是原先觸發 MouseDown 的控制項就不處裡事件 | |
if (sender != DraggingControl) return; | |
// 如果控制項已經移出父控制項範圍就啟動動畫 | |
if (!DraggingControl.IsInsideParent()) | |
MoveBackAnime = DraggingControl.MoveBackInsideParentAsync(); | |
// 清除觸發事件的控制項 | |
DraggingControl = null; | |
} | |
void Panel_Resize(object sender, EventArgs e) | |
{ | |
if (MoveBackAnime != null && !MoveBackAnime.IsCompleted) return; | |
var control = (sender as Control); | |
if (control == null) return; | |
var animations = control.Controls | |
.OfType<Control>() | |
.Where(c => !c.IsInsideParent()) | |
.Select(c => c.MoveBackInsideParentAsync()) | |
.ToArray(); | |
MoveBackAnime = Task.WhenAll(animations); | |
} | |
} | |
static class Extensions | |
{ | |
public static Point Add(this Point p1, Point p2) => new Point(p1.X + p2.X, p1.Y + p2.Y); | |
public static Point Subtract(this Point p1, Point p2) => new Point(p1.X - p2.X, p1.Y - p2.Y); | |
/// <summary> | |
/// 判斷控制項的螢幕範圍是否在父控制項的螢幕範圍內 | |
/// </summary> | |
public static bool IsInsideParent(this Control control) | |
{ | |
var parent = control.Parent; | |
if (parent == null) return false; | |
var parentBounds = parent.RectangleToScreen(Rectangle.Empty); | |
var controlBounds = control.RectangleToScreen(Rectangle.Empty); | |
return parentBounds.Contains(controlBounds); | |
} | |
/// <summary> | |
/// 將控制項移回父控制項的範圍內 | |
/// </summary> | |
public static async Task MoveBackInsideParentAsync(this Control control) | |
{ | |
var parent = control.Parent; | |
if (parent == null) return; | |
while (!control.IsInsideParent()) | |
{ | |
var offset = new Point(); | |
if (parent.Width > control.Width) | |
{ | |
if (control.Left < 0) offset.X += 1; | |
if (control.Right > parent.Width) offset.X -= 1; | |
} | |
else | |
offset.X = -control.Left; | |
if (parent.Height > control.Height) | |
{ | |
if (control.Top < 0) offset.Y += 1; | |
if (control.Bottom > parent.Height) offset.Y -= 1; | |
} | |
else | |
offset.Y = -control.Top; | |
if (offset == Point.Empty) | |
return; | |
control.Location = control.Location.Add(offset); | |
await Task.Yield(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment