C# Windows窗体的明显滞后 形势

C# Windows窗体的明显滞后 形势,c#,windows,forms,graphics,C#,Windows,Forms,Graphics,作为业余爱好,我决定冒险模拟一个难以置信的基本现实。我的想法是创造一个简单的宇宙,里面的物体会根据位置或速度等因素相互作用。人们可以给物体加一个力,使其移动。所以为了展示这个“宇宙”,我必须学习窗户的形状。因此,我对这个话题的了解是有限的。我创建了一个计时器对象,这样在每一段时间后,我都会调用宇宙的渲染来生成位图,并将picturebox设置为该位图。要渲染宇宙,我只需将每个对象渲染到位图上,从而创建几乎所有对象的拼贴 为了测试这一点,我创建了一个宇宙,并添加了一个块。每次更新图像时,我都会根据

作为业余爱好,我决定冒险模拟一个难以置信的基本现实。我的想法是创造一个简单的宇宙,里面的物体会根据位置或速度等因素相互作用。人们可以给物体加一个力,使其移动。所以为了展示这个“宇宙”,我必须学习窗户的形状。因此,我对这个话题的了解是有限的。我创建了一个计时器对象,这样在每一段时间后,我都会调用宇宙的渲染来生成位图,并将picturebox设置为该位图。要渲染宇宙,我只需将每个对象渲染到位图上,从而创建几乎所有对象的拼贴

为了测试这一点,我创建了一个宇宙,并添加了一个块。每次更新图像时,我都会根据按键向块添加一个力。然而,我遇到的是,这个程序有很多视觉上的“滞后”。有时它会运行得非常平稳,但几秒钟后它会冻结半秒钟,然后重新开始工作。这是有点明显,虽然不是难以置信的坏。无论如何,这是我想要解决的问题,和/或至少知道为什么这会成为一个更好的程序员

我已经研究过修复方法,但没有成功。我包括了一些东西,比如双缓冲区和使用后处理图形和位图对象,但这几乎并没有效果

问题 是什么导致了这种“滞后”,我能做些什么来修复它

代码
类块
{
...
公共元组维度{get;}
公共元组位置{get;set;}
...
公共无效渲染(位图)
{
使用(Graphics g=Graphics.FromImage(位图))
{
int x=位图宽度/2;
int y=位图高度/2;
//g、 绘图矩形(钢笔,黑色,0,0,500,500);
g、 DrawRectangle(Pens.DarkCyan,Position.Item1+x-Dimension.Item1/2,Position.Item2+y-Dimension.Item2/2,
维度1,维度2);
}
}
}
阶级宇宙
{
...
公开列出对象;
公共元组维度;
...
公共位图RenderUniverse()
{
位图位图=新位图(Dimensions.Item1,Dimensions.Item2);
foreach(对象中的var obj)
{
对象渲染(位图);
}
返回位图;
}
}
公共部分类Form1:Form
{
宇宙;
const int SPF=50;//距离下一次更新还有几毫秒
公共表格1()
{
定时器=新定时器();
timer.Tick+=更新;
定时器间隔=SPF;
timer.Start();
...
初始化组件();
双缓冲=真;
SetStyle(ControlStyles.OptimizedDoubleBuffer,true);
this.ClientSize=new System.Drawing.Size(Universe.Dimensions.Item1,Universe.Dimensions.Item2);
this.pictureBox1.Size=新系统.Drawing.Size(Universe.Dimensions.Item1,Universe.Dimensions.Item2);
this.pictureBox1.SizeMode=PictureBoxSizeMode.Normal;
}
公共无效更新(对象o、事件args args)
{
...
if(pictureBox1.Equals(null))
pictureBox1.Image.Dispose();
pictureBox1.Image=Universe.RenderUniverse();
pictureBox1.Refresh();
}
}
编辑:
我现在已经拿走了画盒,用在油漆上了。但是,这并没有解决问题。

我怀疑您试图清理旧图像的代码:

if (pictureBox1.Equals(null))
    pictureBox1.Image.Dispose();
pictureBox1.Image = Universe.RenderUniverse();
(特别是第一行)没有做你认为它做的事情。你的评论表明你相信第一句话:

检查pictureBox1是否已经有位图(即它是 (我第一次渲染宇宙)

但它实际上所做的是检查
pictureBox1
是否为
null
(它从未出现在代码流中)

要解决此问题,请改用:

var oldImage = pictureBox1.Image;
pictureBox1.Image = Universe.RenderUniverse();
oldImage?.Dispose();

我不会使用图片框,而是直接在表面上绘制。直接在窗体上绘制,或插入用作框架的控件(例如面板)并在其上绘制

您应该在绘制事件中绘制的对象。让我们在表单本身上绘制:

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();

完整示例(使用命名空间
System.Drawing
中的
PointF
SizeF
结构,而不是元组):

接口对象
{
SizeF维度{get;}
PointF位置{get;set;}
无效渲染(图形g、整数x、整数y);
}
类块:IOObject
{
公共点F位置{get;set;}
公共SizeF维度{get;set;}
公共无效渲染(图形g、整数x、整数y)
{
g、 绘图矩形(
彭斯·达克钦,
位置X+X-尺寸宽度/2,
位置Y+Y-尺寸高度/2,
尺寸,宽度,
尺寸、高度);
}
}
公共部分类FRMPainForm:表单
{
专用计时器_Timer=新计时器();
私有IObject\u currentObject;
私有列表_对象=新列表{
新块{Position=new PointF(0,0),Dimension=new SizeF(50,50)},
新块{Position=new PointF(10,0),Dimension=new SizeF(50,50)},
新块{Position=new PointF(20,0),Dimension=new SizeF(50,50)},
新块{Position=new PointF(20,10),Dimension=new SizeF(50,50)},
新块{Position=new PointF(20,20),Dimension=new SizeF(50,50)},
新块{Position=new PointF(20,30),Dimension=new SizeF(50,50)},
新块{Positi
Refresh();
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);
        }
    }
}