作为一个爱好项目,我决定冒险模拟一个令人难以置信的基本现实。我的想法是创建一个简单的宇宙,其中内部的对象将根据位置或速度等内容进行交互。可以向对象添加力以使其移动。所以要展示这个宇宙'我必须学习窗口的形式。因此,我对这个主题的了解是有限的。我创建了一个计时器对象,以便在每个时间段之后,我会调用Universe的渲染来生成位图并将图片框设置为该位图。为了渲染宇宙,我只是将每个对象渲染到一个位图上,因此几乎可以创建一个对象拼贴。
为了测试这个,我创建了一个Universe,并添加了一个块。在每次更新图像时,我都会根据按键向块添加一个力。然而,我遇到的是该程序有很多视觉和延迟。有时它会非常顺利地运行,但几秒钟后它会冻结半秒钟,然后再次恢复工作。这有点引人注目,虽然不是很糟糕。无论如何,这是我想要解决的问题,并且/或者至少知道为什么这会成为更好的程序员。
我已经研究过修复,但没有成功。我在使用后包含了双缓冲区和处理图形和位图对象之类的东西,但这几乎没有效果。
Just the image of what the forms displays - nothing intersting
造成这种“延迟”的原因是什么?我该怎么做才能解决它?
class Block
{
...
public Tuple<float, float> Dimension { get; }
public Tuple<float, float> Position { get; set; }
...
public void Render(Bitmap bitmap)
{
using (Graphics g = Graphics.FromImage(bitmap))
{
int x = bitmap.Width / 2;
int y = bitmap.Height / 2;
//g.DrawRectangle(Pens.Black, 0, 0, 500, 500);
g.DrawRectangle(Pens.DarkCyan, Position.Item1 + x - Dimension.Item1 / 2, Position.Item2 + y - Dimension.Item2 / 2,
Dimension.Item1, Dimension.Item2);
}
}
}
class Universe
{
...
public List<IObject> Objects;
public Tuple<int, int> Dimensions;
...
public Bitmap RenderUniverse()
{
Bitmap bitmap = new Bitmap(Dimensions.Item1, Dimensions.Item2);
foreach (var obj in Objects)
{
obj.Render(bitmap);
}
return bitmap;
}
}
public partial class Form1 : Form
{
Universe Universe;
const int SPF = 50; //MILLISECONDS TILL NEXT UPDATE
public Form1()
{
Timer timer = new Timer();
timer.Tick += Update;
timer.Interval = SPF;
timer.Start();
...
InitializeComponent();
DoubleBuffered = true;
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.ClientSize = new System.Drawing.Size(Universe.Dimensions.Item1, Universe.Dimensions.Item2);
this.pictureBox1.Size = new System.Drawing.Size(Universe.Dimensions.Item1, Universe.Dimensions.Item2);
this.pictureBox1.SizeMode = PictureBoxSizeMode.Normal;
}
public void Update(object o, EventArgs args)
{
...
if (pictureBox1.Equals(null))
pictureBox1.Image.Dispose();
pictureBox1.Image = Universe.RenderUniverse();
pictureBox1.Refresh();
}
}
我现在已经拿走了图片框并使用了OnPaint。但是,这还没有解决问题。
答案 0 :(得分:0)
我怀疑你的代码试图清理旧图像:
if (pictureBox1.Equals(null))
pictureBox1.Image.Dispose();
pictureBox1.Image = Universe.RenderUniverse();
(特别是第一行)没有按照你的想法做。您的评论表明您相信第一行:
检查pictureBox1是否已经有位图(即它是 我第一次渲染宇宙)
但实际所做的是检查pictureBox1
是否为null
(它永远不会出现在您的代码流中)。
要解决此问题,请改为使用:
var oldImage = pictureBox1.Image;
pictureBox1.Image = Universe.RenderUniverse();
oldImage?.Dispose();
答案 1 :(得分:0)
我不会使用图片框,而是直接在表面上绘图。直接在表单上绘制或插入用作框架的控件(例如Panel)并在其上绘制。
你应该在Paint事件中绘制。让我们在表格上画画:
protected override void OnPaint(PaintEventArgs e)
{
int x = ClientSize.Width / 2;
int y = ClientSize.Height / 2;
e.Graphics.DrawRectangle(
Pens.DarkCyan,
_currentBlock.Position.Item1 + x - _currentBlock.Dimension.Item1 / 2,
_currentBlock.Position.Item2 + y - _currentBlock.Dimension.Item2 / 2,
_currentBlock.Dimension.Item1,
_currentBlock.Dimension.Item2);
}
然后,只要调用表单的Invalidate
方法或您正在使用的控件,就可以在发生更改时触发绘图:
Invalidate();
这样做的好处是不会出现拥塞,因为如果Windows尚未绘制,它只会调用OnPaint
。
如果您想强制立即更新,请改为呼叫Refresh
:
Refresh();
完整示例(使用命名空间PointF
中的SizeF
和System.Drawing
结构而不是元组:
interface IObject
{
SizeF Dimension { get; }
PointF Position { get; set; }
void Render(Graphics g, int x, int y);
}
class Block : IObject
{
public PointF Position { get; set; }
public SizeF Dimension { get; set; }
public void Render(Graphics g, int x, int y)
{
g.DrawRectangle(
Pens.DarkCyan,
Position.X + x - Dimension.Width / 2,
Position.Y + y - Dimension.Height / 2,
Dimension.Width,
Dimension.Height);
}
}
public partial class frmPaintOnForm : Form
{
private Timer _timer = new Timer();
private IObject _currentObject;
private List<IObject> _objects = new List<IObject> {
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF(10, 0), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF(20, 0), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF(20,10), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF(20,20), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF(20,30), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF(10,30), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF( 0,30), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF( 0,20), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF( 0,20), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(55,50) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(60,50) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(65,50) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(60,50) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(55,50) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,50) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,55) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,60) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,65) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,60) },
new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,55) },
};
private int _index;
public frmPaintOnForm()
{
InitializeComponent();
DoubleBuffered = true;
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
_timer.Interval = 17;
_timer.Tick += Timer_Tick;
_timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
// Select the next object by cycling through the object list and trigger drawing
_currentObject = _objects[_index];
_index = (_index + 1) % _objects.Count;
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
if (_currentObject != null) {
int x = ClientSize.Width / 2;
int y = ClientSize.Height / 2;
_currentObject.Render(e.Graphics, x, y);
}
}
}
请注意,您绝不会直接致电OnPaint
。操作系统(Windows)负责这一点。通过调用Invalidate
,您只需提示Windows,Windows决定何时以及是否将调用OnPaint
。 Windows可以在许多情况下调用它:当窗体被打开或从最小化恢复时,当删除此窗体之上的另一个窗体时,在调整窗体大小时,当然,如果您通过调用{{1}请求它}或Invalidate
。当悬停在任务栏上时,它也可以将其称为绘制形式的微缩图像。如果出现拥塞或表格不可见,Windows也可能会决定省略一些电话。