[Tutorial] Zeichnen in Windows-Forms-Programmen (Paint/OnPaint, PictureBox) [Tutorial] Zeichnen in Windows-Forms-Programmen (Paint/OnPaint, PictureBox) Hallo,
ich bin Anfängfer und bin gerade dabei das Spiel "Asteroids" nachzuprogrammieren.
Es läuft ganz gut, jedoch habe ich ein paar Probleme.
Die Asteroiden sind bei mir Kreise, die ich in einer pictureBox zeichnen lasse, wenn ich auf den "Start"-Button drücke.
Diese lasse ich dann mit einem Timer nach unten bewegen.
Jedoch möchte ich, dass immer nach einem bestimmten Zeitabschnitt wieder neue Kreise gezeichnet werden...
Ich habe das schon mit einem zweiten Timer und einer zweiten pictureBox probiert, aber es will alles nicht hinhauen... 😦
Habt ihr Anhaltspunkte?
Mein Code vom Start-Button:
private void buttonStart_Click(object sender, EventArgs e)
{
timerAsteroid.Start();
Random Rnd = new Random();
int RndNumber2 = Rnd.Next(8, 20);
if (checkBox1.Checked == true)
{
triangle = new Triangle() { X = 300, Y = 550, Vx = (int)numericUpDown4.Value, Vy = (int)numericUpDown4.Value, D1 = (int)numericUpDown5.Value, D2 = (int)numericUpDown6.Value, D3 = (int)numericUpDown7.Value, Alpha = (int)numericUpDown8.Value };
listBox1.Items.Add(triangle);
pictureBox1.Refresh();
}
else if(checkBox2.Checked == true)
{
rectangle = new Rectangle() { X = 300, Y = 555, Width = (int)numericUpDown2.Value, Height = (int)numericUpDown3.Value, Vx = (int)numericUpDown4.Value, Vy = (int)numericUpDown4.Value};
listBox1.Items.Add(rectangle);
pictureBox1.Refresh();
}
for (int i = 0; i < RndNumber2; i++)
{
int rndnumber1 = Rnd.Next(0, 1600);
Asteroid asteroids = new Asteroid() { X = rndnumber1, Radius = (int)numericUpDown1.Value, Height = (int)numericUpDown1.Value * 2, Width = (int)numericUpDown1.Value * 2 };
listBox1.Items.Add(asteroids);
pictureBox1.Refresh();
}
}
Mein Code in Form1.cs (Ausschnitt):
private void timerMoving_Tick(object sender, EventArgs e)
{
TimeSpan elapsed = DateTime.Now - lastPaint;
double resultSeconds = elapsed.TotalSeconds;
List<Shot> toDisposeShot = new List<Shot>();
List<Asteroid> toDisposeAsteroid = new List<Asteroid>();
foreach (Object item in listBox1.Items)
{
Triangle triangle = item as Triangle;
Rectangle rectangle = item as Rectangle;
Asteroid asteroid = item as Asteroid;
Shot shot = item as Shot;
spaceship = item as Spaceship;
#region Zusammenführung
if (item is Spaceship)
{
Spaceship geoObject = item as Spaceship;
if (objPosition == Position.Left)
{
geoObject.Vx -= 10;
}
if (objPosition == Position.Right)
{
geoObject.Vx += 10;
}
if (objPosition == Position.Up)
{
geoObject.Vy -= 10;
}
if (objPosition == Position.Down)
{
geoObject.Vy += 10;
}
if (geoObject.X <= 0 && geoObject.Vx < 0 || geoObject.X <= 0 && geoObject.Vy < 0)
{
geoObject.Vx = 0;
geoObject.Vy = 0;
geoObject.X = 0;
}
if (geoObject.Y <= 0 && geoObject.Vy < 0 || geoObject.Y <= 0 && geoObject.Vx < 0 || geoObject.Y <= 0 && geoObject.Vx > 0)
{
geoObject.Vy = 0;
geoObject.Vx = 0;
geoObject.Y = 0;
}
if (geoObject.X >= pictureBox1.Width - geoObject.Width && geoObject.Vx > 0 || geoObject.X >= pictureBox1.Width - geoObject.Width && geoObject.Vy > 0 || geoObject.X >= pictureBox1.Width - geoObject.Width && geoObject.Vy < 0)
{
geoObject.Vx = 0;
geoObject.Vy = 0;
geoObject.X = pictureBox1.Width - geoObject.Width;
}
if (geoObject.Y >= pictureBox1.Height - geoObject.Height && geoObject.Vy > 0 || geoObject.Y >= pictureBox1.Height - geoObject.Height && geoObject.Vx > 0 || geoObject.Y >= pictureBox1.Height - geoObject.Height && geoObject.Vx < 0)
{
geoObject.Vy = 0;
geoObject.Vx = 0;
geoObject.Y = pictureBox1.Height - geoObject.Height;
}
geoObject.X += geoObject.Vx * resultSeconds;
geoObject.Y += geoObject.Vy * resultSeconds;
// if (geoObject.X < 0)
// {
// geoObject.X = 0;
// }
// if (geoObject.Y < 0)
// {
// geoObject.Y = 0;
// }
// if (geoObject.X > pictureBox1.Width - geoObject.Width)
// {
// geoObject.X = pictureBox1.Width - geoObject.Width;
// }
// if (geoObject.Y > pictureBox1.Height - geoObject.Height)
// {
// geoObject.Y = pictureBox1.Height - geoObject.Height;
// }
}
#endregion
else if (asteroid != null)
{
if (asteroid.Kmy > pictureBox1.Height)
{
toDisposeAsteroid.Add(asteroid);
}
else
{
asteroid.Vy += 2;
asteroid.Y += asteroid.Vy * resultSeconds;
}
}
else if (shot != null)
{
if (objShoot == true)
{
if (shot.X < 0 || shot.Y < 0)
{
toDisposeShot.Add(shot);
}
else
{
shot.Y += shot.Vy * resultSeconds;
shot.X += shot.Vx * resultSeconds;
}
}
}
}
lastPaint = DateTime.Now;
for (int i = 0; i < toDisposeShot.Count; i++)
{
var shot = toDisposeShot[i];
listBox1.Items.Remove(shot);
shot.Dispose();
}
for (int i = 0; i < toDisposeAsteroid.Count; i++)
{
var asteroid = toDisposeAsteroid[i];
listBox1.Items.Remove(asteroid);
asteroid.Dispose();
}
//pictureBox1.Refresh();
UpdatePictureBox();
}
public void timerAsteroid_Tick(object sender, EventArgs e)
{
// if (asteroid != null)
// {
// Random Rnd = new Random();
// int RndNumber3 = Rnd.Next(8, 30);
// List<GeometricObject> NewAsteroids = new List<GeometricObject>();
// for (int i = 0; i < RndNumber3; i++)
// {
// int RndNumber1 = Rnd.Next(0, 1600);
// asteroid = new Asteroid() { X = RndNumber1, Radius = (int)numericUpDown1.Value, Height = (int)numericUpDown1.Value * 2, Width = (int)numericUpDown1.Value * 2 };
// listBox1.Items.Add(asteroid);
// //NewAsteroids.Add(asteroid);
// foreach (object item in listBox1.Items)
// {
// GeometricObject asteroid = item as GeometricObject;
// if (asteroid != null)
// {
// pictureBox2.Refresh();
// }
// }
// UpdatePictureBox();
// }
//}
////pictureBox1.Refresh();
//UpdatePictureBox();
pictureBox2.Refresh();
}
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
List<GeometricObject> NewAsteroid = new List<GeometricObject>();
Random Rnd = new Random();
int RndNumber1 = Rnd.Next(0, 1600);
asteroid = new Asteroid() { X = RndNumber1, Radius = (int)numericUpDown1.Value, Height = (int)numericUpDown1.Value * 2, Width = (int)numericUpDown1.Value * 2 };
NewAsteroid.Add(asteroid);
}
private void UpdatePictureBox()
{
if (pictureBox1.Image == null) pictureBox1.Image = new Bitmap(2400, 2050);
List<GeometricObject> toDeleteSpaceship = new List<GeometricObject>();
using (var g = Graphics.FromImage(pictureBox1.Image))
{
g.DrawImage(Resources.weltraum001_1400x1050, 0, 0);
// grap all astroids
List<GeometricObject> astroids = new List<GeometricObject>();
foreach (var item in listBox1.Items)
{
if (item is Asteroid)
{
astroids.Add((Asteroid)item);
}
}
foreach (Object item in listBox1.Items)
{
Spaceship rectangle = item as Spaceship;
Spaceship triangle = item as Spaceship;
Spaceship spaceship = item as Spaceship;
GeometricObject asteroid = item as GeometricObject;
GeometricObject shot = item as GeometricObject;
if (triangle != null)
{
triangle.Draw(g);
}
if (rectangle != null)
{
rectangle.Draw(g);
}
if (asteroid != null)
{
asteroid.Draw(g);
}
if (item is Spaceship)
{
var collison = ((Spaceship)item).CheckCollison(astroids);
if (collison != null)
{
toDisposeSpaceship.Add(spaceship);
MessageBox.Show("Bum, das Raumschiff wurde zerstört durch: " + collison.Object2.ToString());
//toDeleteAfterShot();
}
if (item is Shot)
{
if (objShoot == true)
{
shot.Draw(g);
}
}
}
}
pictureBox1.Refresh();
}
}
Gruß und Danke im Vorraus! 😄
Beschäftige Dich mal mit dem Thema Debugging/Debugger - damit solltest Du den/die Fehler finden können. Ich denke nicht, dass Dir hier jemand diese Arbeit abnehmen möchte/wird...
Wenn ich debugge, geht der Programmablauf gar nicht erst in den "TimerAsteroid_Tick"-Event hinein....
Es muss ja auch keiner eine Komplettlösung hier schreiben.
Aber ein paar Anhaltspunkte, wie ich das schaffen könnte wäre echt nett. 😃
Hast du evtl. den Timer nicht gestartet oder den EventHandler nicht ans Event gehängt?
Hallo RingYK,
du startest den Timer zwar, aber sagst ihm nie, dass er beim Ablauf auch in die Methode soll. Zumindest ist das in dem Code oben nicht ersichtlich.
Gruss
Coffeebean
Microsoft MVP // Me // Blog // GitHub // @Egghead // All my talks // Speakerdeck
Hallo @Coffeebean,
Ich dachte mit "pictureBox2.Refresh()" sage ich, dass er dahingehen soll?
In der pictureBox habe ich noch hinzugefügt:
foreach (object item in NewAsteroid)
{
GeometricObject asteroid = item as GeometricObject;
if (asteroid != null)
{
asteroid.Draw(e.Graphics);
}
}
pictureBox2.Refresh();
Leider klappt auch das nicht...
Coffeebean meint, ob du den Eventhandler für das Ticken des Timers angemeldet hat.
Timer.Tick-Ereignis
Also in meinem Form1.Designer.cs sieht es folgendermaßen aus:
// timerAsteroid
//
this.timerAsteroid.Enabled = true;
this.timerAsteroid.Interval = 4000;
this.timerAsteroid.Tick += new System.EventHandler(this.timerAsteroid_Tick);
//
Mit "timerMoving" sieht es auch so aus und der funktioniert ja...
Hast Du mal den Debugger aufgerufen, ob überhaupt das in der Reihenfolge und mit den Informationen ausgeführt wird, wie Du Dir das im Kopf vorstellst?
[Artikel] Debugger: Wie verwende ich den von Visual Studio?
Zur Not mal Dir den Workflow auf ein Blatt papier, dann sieht man oft einen Denkfehler leicht als im Code.
Danach Debugger anwerfen und schauen, ob der erdachte Workflow so auch umgesetzt wurde.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
this.timerAsteroid.Enabled = true;
Auch wenn es wahrscheinlich nicht das Problem löst:
Wenn ein Timer manuell (per Start-Methode) gestartet werden soll, dann muss Enabled
auf jeden Fall erstmal auf false
stehen - ist der Wert true
, startet der Timer sofort.
Danke @p!lle, ich habe es angepasst. ^^
Soo, jetzt habe ich es soweit, dass er in den Timer geht, auf einen neuen Kreis verweist und dann in die Draw-Methode des Kreises hineingeht. Aber genau dann gibt er mir eine Null-Referenz züruck...
Also: > Fehlermeldung:
Additional information: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
Hier mein Code:
public void timerAsteroid_Tick(object sender, EventArgs e)
{
NewAsteroids(g);
}
public void NewAsteroids(Graphics g)
{
List<GeometricObject> NewAsteroid = new List<GeometricObject>();
Random Rnd = new Random();
int RndNumber1 = Rnd.Next(0, 1600);
Asteroid asteroid = new Asteroid() { X = RndNumber1, Radius = (int)numericUpDown1.Value, Height = (int)numericUpDown1.Value * 2, Width = (int)numericUpDown1.Value * 2 };
NewAsteroid.Add(asteroid);
foreach (object item in NewAsteroid)
{
//GeometricObject asteroid = item as GeometricObject;
if (asteroid != null)
{
asteroid.Draw(g);
}
}
}
Und meine Draw-Methode in der Klasse "Asteroid":
public class Asteroid : GeometricObject, IDisposable
{
public override void Draw(Graphics g)
{
g.FillEllipse(Brushes.Brown, (float)X, (float)Y, (float)Height, (float)Width);
//g.DrawLine(Pens.Beige, (float)Kmx, (float)Kmy, (float)Kmx + (float)radius, (float)Kmy + (float)radius);
}
g.FillEllipse(Brushes.Brown, (float)X, (float)Y, (float)Height, (float)Width); ---> Hier kommt die Null-Referenz.
Ich frage mich wieso, da ich doch überall die Kreise instanziiert habe?
Oder könnte das an etwas anderem liegen?
Gruß
Das findest du am Schnellsten über den [Artikel] Debugger: Wie verwende ich den von Visual Studio? raus.
Beachte außerdem: [FAQ] NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt
=)
Ich find bei deim Code schlecht gelöst ist, dass du deine ZeichenObjekte in einer Listbox verwaltest.
In diesem Listbox-Control kann ja eiglich nichts vernünftiges angezeigt werden.
Da nimm lieber eine List<Object> - die ist eh kein Gui-Element.
Auch kannst du bei diesem Problem sehr schön Vererbungslehre und OOP walten lassen:
Schaff dir eine abstrakte Basisklasse "ZeichenObjekt", von der deine verschiedenen ZeichenObjekte erben, und die je nach ihrer Art die Basis-Zeichen-Methoden überschreiben.
Dann kann jedes Objekt - egal welcher Art - sich selber zeichnen, mit seiner je spezifischen Überschreibung, und die ganzen Typ-Überprüfungen sind überflüssig.
Tu die ZeichenObjekte dann nicht in eine List<Object>, sondern in eine List<ZeichenObjekt>.
Hier ist sowas vorgeturnt mit noch paar weiteren Schikanen: Performantes OwnerDrawing
Der frühe Apfel fängt den Wurm.