我做了一个C#的winform程序的小游戏,游戏是这样的:界面上有若干小点和一个小人,所有小点每三秒钟随机变化一次位置,如果小点碰到小人就算游戏结束了,在这三秒钟内你也可以用鼠标拖动小人来选择一个你觉得可能不会被碰撞的位置。其实这个游戏坚持的时间长短完全是靠运气,毕竟小点位置是随机的,并不是有规律可言的。下面记录我在做这个小游戏过程遇到的问题和解决的方式,如果有对这个游戏感兴趣的朋友私我要代码,其实很简单,会了下面我说的这几点知识你一定也可以做出来。
1、线程间操作无效: 从不是创建控件“xxx”的线程访问它。
先来看一段程序:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void Setlocation(PictureBox p)
{
p.Left = 200;
p.Top = 200;
}
private void button1_Click(object sender, EventArgs e)
{
PictureBox p = new PictureBox();
p.Image = Image.FromFile("bullet_blue.png");
p.Left = 50;
p.Top = 50;
this.Controls.Add(p);
Thread t1=new Thread(()=>Setlocation(p));
t1.Start();
}
}
通过一个按钮来添加一个picturebox控件,然后通过一个新的线程来修改该控件的位置,运行程序就会提示“线程间操作无效: 从不是创建控件“xxx”的线程访问它”。因为你通过一个不是创建该控件的线程来修改此控件的状态了,这是在.net2之后不允许的。因为多线程同时操作某一控件的状态可能导致该控件处于不一致的状态。
解决办法有两个:
在窗体的构造函数中添加:Control.CheckForIllegalCrossThreadCalls = false ;public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
}
这样可以使系统不去检查你是否跨线程访问控件了,但是这并不是标准的解决方法,因为系统之所以会去检查是因为怕你在后面的程序中出错,比如你两个线程同时修改同一个控件的状态;而你不让系统去检查,那么当你后续程序运行真的有多线程同时操作某一控件的状态的情况时,会出现更大的错误。
通过委托来实现:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
}
public delegate void SetDelegate(PictureBox p);
public void Setlocation(PictureBox p)
{
if(p.InvokeRequired)
{
SetDelegate s = new SetDelegate(Setlocation);
this.BeginInvoke(s, new object[] { p });
}
else
{
p.Left = 200;
p.Top = 200;
}
}
private void button1_Click(object sender, EventArgs e)
{
PictureBox p = new PictureBox();
p.Image = Image.FromFile("bullet_blue.png");
p.Left = 50;
p.Top = 50;
this.Controls.Add(p);
Thread t1=new Thread(()=>Setlocation(p));
t1.Start();
}
}
给修改控件的函数setlocation写一个委托,通过判断InvokeRequired可以知道是否需要用委托来调用当前控件的一些方法,如果需要那么把这个方法和参数封装之后传送给控件所在线程去执行。
2、定时器Timer类:
网上使用方式很多,我这里主要介绍一种定时器绑定的事件带有参数的(非系统参数):
public partial class initialize : Form
{
System.Timers.Timer aTimer = new System.Timers.Timer();//定时器对象
public void settimer(PictureBox[] arr)
{
//aTimer.Elapsed += new ElapsedEventHandler(Movement);
aTimer.Elapsed += new ElapsedEventHandler((s,e)=>Movement(s,e,arr));//传递参数arr
aTimer.Interval = 2000;//执行事件的间隔,单位毫秒
aTimer.AutoReset = true;//执行一次变false,一直执行true
//是否执行System.Timers.Timer.Elapsed事件
aTimer.Enabled = true;
}
public void Movement(object source, System.Timers.ElapsedEventArgs e,PictureBox[] arr)//Movement函数参数列表
}
想关闭定时器的方法为:aTimer.Stop();
系统自定义的ElapsedEventHandler为:
public delegate void ElapsedEventHandler(
object sender,
ElapsedEventArgs e
)
可以参考这篇文章 C#.NET 定时器类及使用方法写得非常好,C#程序中三种定时器类的介绍都有,还有具体用法。
3、用鼠标拖动控件:
以拖动button为例,当鼠标按下button时记录鼠标与button的实际坐标,并用一个bool变量记录当前鼠标状态为mousedown;
然后在鼠标移动事件中拿当前鼠标实际坐标,与mouseDown时记录的坐标的偏差来加上button原坐标,便是现在的坐标;在鼠标抬起时,清除状态。
//是否在拖拽
bool isDrag = false;
//鼠标相对于button控件左上角的坐标
Point contextbtnPoint = Point.Empty;
//将被拖动的控件
private Control control;
private void button1_MouseDown(object sender, MouseEventArgs e)
{
isDrag = true;
control = button1;
if (e.Button == MouseButtons.Left)
{
contextbtnPoint = button1.PointToClient(Control.MousePosition); //鼠标相对于button左上角的坐标
}
}
private void button1_MouseMove(object sender, MouseEventArgs e)
{
if (isDrag)
{
Point formPoint = this.PointToClient(Control.MousePosition);//鼠标相对于窗体左上角的坐标
int x = formPoint.X - contextbtnPoint.X;
int y = formPoint.Y - contextbtnPoint.Y;
control.Location = new Point(x, y);
}
}
private void button1_MouseUp(object sender, MouseEventArgs e)
{
if (isDrag)
{
isDrag = false;
}
}
关于获取鼠标坐标的方法有下面三种:
Point screenPoint = Control.MousePosition;//鼠标相对于屏幕左上角的坐标
Point formPoint = this.PointToClient(Control.MousePosition);//鼠标相对于窗体左上角的坐标
Point contextMenuPoint = contextMenuStrip1.PointToClient(Control.MousePosition); //鼠标相对于contextMenuStrip1左上角的坐标
4、长方形是否相交问题:
Rectangle rectangle1 = new Rectangle(50, 50, 200, 100);//参数分别为长方形的横坐标,纵坐标,宽度,高度 Rectangle rectangle2 = new Rectangle(70, 20, 100, 200);
if (rectangle1.IntersectsWith(rectangle2))
//相交
else
//不相交