Laden...

Optimierungsideen für die Darstellung von sehr vielen Controls?

Erstellt von Lynix vor 19 Jahren Letzter Beitrag vor 15 Jahren 14.034 Views
L
Lynix Themenstarter:in
667 Beiträge seit 2004
vor 19 Jahren
Optimierungsideen für die Darstellung von sehr vielen Controls?

Hallo zusammen !

Also ich habe folgendes Problem.

Im folgenden Testprogramm, welches ich mir gebastelt hab, erstelle ich eine Art Spielfeld, welches eine variable Größe haben kann.

Hier das Programm : Testprogramm

Die einzelnen Felder werden durch Usercontrols dargestellt, da diese später mittels Rechts-/Linksklick bestimmte Aktionen bereitstellen sollen.

Das Problem liegt nun darin, dass es sehr lange dauert z.B. ein Feld der Größe 100 x 100 zu erstellen. Hier bräuchte ich Ideen / Erfahrungen Eurerseits wo ich hier Zeit einsparen könnte.

Hier mal noch die betreffenden Codezeilen :

Die Funktion zum Initialisieren des Feldes (wird durch Druck des Buttons ausgeführt) :


public void InitializeGrid(int lengthX, int lengthY, ITile tileType)
{
   this.Controls.Clear();

   for(int i=0; i<lengthX; i++)
   {
       for(int j=0; j<lengthY; j++)
       {
          // nächstes Tile anlegen
          ITile newTile = tileType.Create(tileType.TileWidth, tileType.TileHeight);

          // Position des neuen Tiles im Grid festlegen
          newTile.TileLocation = 
             new System.Drawing.Point(i*tileType.TileWidth,j*tileType.TileHeight);

          // Neues Tile in den Container einfügen
          this.Controls.Add((System.Windows.Forms.Control)newTile);

          // Tile anzeigen
          newTile.ShowTile();
	}
    }
}

Und hier die Paint-Methode der Klasse die in diesem Fall das ITile-Interface implementiert :


private void CustomTile_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
   int breite = this.Width;
   int hoehe = this.Height;
   System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath();

   gp.AddLine(0, 0, breite - 1, 0);
   gp.AddLine(breite - 1, 1, breite - 1, hoehe - 1);
   gp.AddLine(breite - 1, hoehe - 1, 1, hoehe - 1);
   gp.AddLine(0, hoehe - 1, 0, 1);
   gp.CloseFigure();

   e.Graphics.FillPath(new SolidBrush(Color.WhiteSmoke),	gp );
}

Ziel der Übung soll am Ende eine kleine Entwicklungsumgebung für Brettspielchen wie Mensch ärgere Dich nicht oder Reversi sein (falls jemand Lust hat mitzumachen, bitte melden g).

Also ich danke schonmal vorab für alle Tips !

"It is not wise to be wise" - Sun Tzu

S
238 Beiträge seit 2004
vor 19 Jahren

Hmm, solche Ladeprobleme kenn ich noch aus meiner VB-Zeit, wenn ich ähnliche Programme versucht hab (z.B. Kreuzworträtsel g).
Gut funktioniert hat immer, wenn man die anzeigende Form erst nach dem vollständigen Laden anzeigte.
Die Controls ebenfalls.
Das spart Ressourcen, würde ich sagen.
Ansonsten vielleicht mit Assemblercode, aber da kann ich dir momentan leider noch nicht weiterhelfen - steht auf meiner Lernliste - aber ziemlich weit unten g

Greets - SK

Sagte ich schon Danke? Nein? ...kommt noch...

L
Lynix Themenstarter:in
667 Beiträge seit 2004
vor 19 Jahren

Hi !

Also das Anzeigen erst wenn alle Controls angelegt wurden hab ich schon versucht, indem ich erst alle Controls in ein Controls[] - Array gepackt hab und dann versucht habe das Array mittels AddRange() in die Controls-Collection einzufügen.

Das ging dann zwar auch ohne Wartezeit, aber dafür waren die ganzen Eigenschaften von den einzelnen Controls futsch, z.B. die Location war komplett zufällig 😦

"It is not wise to be wise" - Sun Tzu

X
2.051 Beiträge seit 2004
vor 19 Jahren

ich würde gar keine Controls für einzelne Zellen machen. Sondern nur Klassen die Information über die Felder festhalten und eventuel mit dem Parentform kommunizieren. die Darstellung/Zeichnen der Felder ist die Sache des Parenform. genau so die Auswertung der Mouse- bzw. Tastaturereignisse.

F
124 Beiträge seit 2004
vor 19 Jahren

also assembler ist erstmal völliger quatsch.
a) bist du in .net
b) selbst in c++ mit mfc oder so würde wohl kaum einer auf die idee kommen, assembler zu nutzen

naja und das mit dem erst voll laden, dann anzeigen, macht eine windows form immer! es gibt da nähmlich die funktionen suspendlayout und resumelayout. guck mal in deine InitializeComponent() !

wenn du deine usercontrols natürlich mit code und nicht im designer hinzufügst, dann mach halt so:


this.SuspendLayout();
//bzw
dasPanelWoDeinGridDrinIst.SuspendLayout();

// hier deine controls anfügen

this.ResumeLayout();
//bzw
meinPanel.....ResumeLayout();

ich habe gerade geguckt, das datagrid unterstützt auch die beiden methoden. ruf die mal vom datagrid lieber auf.

L
Lynix Themenstarter:in
667 Beiträge seit 2004
vor 19 Jahren

Also ich hab hier auf der Arbeit grad mal in der MSDN nach SuspendLayout und ResumeLayout geschaut und es sieht anhand der Beispiele ziemlich danach aus als wär das genau das was ich brauche 🙂

Ich werds heute Abend mal ausprobieren wenn ich daheim bin, danke auf jeden Fall mal 🙂

"It is not wise to be wise" - Sun Tzu

49.485 Beiträge seit 2005
vor 19 Jahren

Hallo Lynix,

keine Ahnung, ob mein Vorschlag was an der Performance ändert und ich weiß auch nicht genau, was du in den Tiles so veranstalten willst, aber ich wollte trotzdem auf die Control-Klasse PictureBox, die ein Image (z.B. Bitmap) darstellen, kann hinweisen. Wenn die Tiles nur Images/Bitmaps darstellen sollen, kannst du dir so das "Selberzeichnen" deines Usercontrols sparen. Wenn die Möglichten einer PictureBox nicht ausreichen, wäre PictureBox zumindest eine überlegenswerte Basisklasse für Dein Control.

Wenn alle Stricke reißen, kannst du statt der vielen Tiles auch eine große PictureBox für das Spielfeld verwenden (das Prinzip Spielfeld statt Tiles wurde ja schon vorgeschlagen). Das Zeichnen in das der PictureBox zugeordneten Image kannst du mit der Klasse Graphics erledigen.

Ich sehe denn Vorteil in der PictureBox darin, dass man sich auf einer Zeichenfläche (Image) austoben kann und sich um den ganzen Darstellungs-, Invalidate-, Paint-, ClipRectangle-Kram gar nicht kümmern muss (na ok, Invalidate braucht u.U. man immer noch, kann sich aber günstigenfalls auf die Version ohne Parameter beschränken).

HTH

herbivore

L
Lynix Themenstarter:in
667 Beiträge seit 2004
vor 19 Jahren

Hallo nochmal,

Leider hat es mit SuspendLayout und ResumeLayout nicht funktioniert. Es dauert noch immer unheimlich lange. Die Wartezeit scheint irgendwie mit dem Hinzufügen der einzelnen Controls in die Form.Controls - Collection zu tun zu haben. Selbst wenn ich die Controls vor dem Hinzufügen auf Visible=false setze, werden sie zwar nicht angezeigt aber die Anwendung hängt trotzdem 🙁

@herbivore
Deine Spielfeldvariante hilft mir leider nicht weiter, da für jedes Tile gewisse Aktionen mittels eines ContextMenü ausgeführt werden können müssen (witziger Satz lol).
Ausserdem sollte es möglich sein, jedem Teil bestimmte Eigenschaften zuzuweisen. Also es geht nicht nur rein um das Zeichnen.

Hat sonst noch jemand eine Idee ? Kann doch nicht sein, dass sowas noch nie gemacht worden ist.

"It is not wise to be wise" - Sun Tzu

X
2.051 Beiträge seit 2004
vor 19 Jahren

natürlich geht es so wie herbivore oder auch ich geschrieben haben.

Jedes Tile muss Koordinaten und Größe besitzen. Bei einem Mouseclick auf das Spielfeld kannst du über die Position des Mousecursors und Tile-Koordinaten ermitteln auf welchen Tile sich die folgende Aktion auswirken soll.

Das ist zwar ganz gewaltige Änderung in deim Prog (na ja neu schreiben 🙂), aber bringt dir auch was.

49.485 Beiträge seit 2005
vor 19 Jahren

Hallo Lynix,

ich habe jetzt auch mal ein kleines Programm geschrieben. Es erzeugt im Konstruktor des Forms ein Grid von PictureBoxen (10x10 Pixel) und fügt jede PictureBox direkt dem Form hinzu.

Bei 10x10 Controls benötigt das Programm zum Starten unter 0,5 Sekunden (bis es komplett dargestellt ist. Bei 50x50 Control dauert der gleiche Vorgang schon 4,5 Sekunden und das Fenster reagiert sehr träge, wenn es neu gezeichnet werden muss. Bei 100x100 bricht das Programm mit einer Exception ab.

Damit dürfte die Variante, bei der jedes Tile ein eigenes Control aus meiner Sicht (zumindest bei der jetzigen Windows- und Rechnergeneration) aus dem Rennen sein.

Also zurück zu dem von Xqgene vorgeschlagenem Spielfeld-Control. Schon in seiner ersten Nachricht beschreibt er ziemlich genau, was du machen musst. Du musst halt in den EventHandlern aus den gelieferten Koordinaten selbst ausrechnen, welches Tile betroffen ist, und dann so reagieren, wie es für dieses Tile vorgesehen ist. Andersherum musst Du beim Zeichnen von Tiles zu den Koordinaten innerhalb des Tiles die Offsets vom Spielfeldrand zu dem entsprechenden Tile hinzurechnen.

HTH

herbivore

L
Lynix Themenstarter:in
667 Beiträge seit 2004
vor 19 Jahren

Hm, sorry, ich kann mir nicht vorstellen, wie ich Eigenschaften und Contextmenüs zu Tiles hinzufügen soll die keine eigenen Objekte darstellen. Sicher krieg ich raus wo ich mit der Maus geklickt habe (Koordinaten) aber wie soll ich nun ein Objekt das an Position 5/10 steht durch Klick z.B. aktivieren wenn es sich dabei garnet um ein Objekt handelt ?

Oder wie soll ich z.B. einer Kiste die auf einem bestimmten Feld steht eine Aktion zuweisen, wenn die Kiste kein Objekt ist sondern nur eine Graphik an einer Position innerhalb meines Spielfelds oder wegen mir Picturebox ?

Es geht ja net nur um das Zeichnen von Grafiken irgendwo auf dem Spielfeld, sondern die einzelnen Objekte die sich an einer beliebigen Position im Spielfeld aufhalten können, sollen alle eigene Fähigkeiten, eigene Aktionen etc. haben können. Natürlich sollen auch zwei Objekte, die sich irgendwo auf der Karte treffen miteinander interagieren können (z.B. Objekt Schlumpf nimmt Pilz) wobei in dem Beispiel Schlumpf und Pilz jeweils ein eigenes Objekt wären.

Ich weiss net ob ich es zu unklar ausdrücke oder ausgedrückt habe, aber meiner Meinung nach ist all das oben Beschriebene ohne eigene Objekte für die einzelnen "Dinge" auf dem Spielfeld net realisierbar.

"It is not wise to be wise" - Sun Tzu

X
2.051 Beiträge seit 2004
vor 19 Jahren

hier ein kleines Bsp., das dir zeigt wie so was umgesetzt wird:

 
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace WindowsApplication1
{
	public class Form1 : System.Windows.Forms.Form
	{
		private System.ComponentModel.Container components = null;
		BaseTile [] tiles = null;
		private System.Windows.Forms.ContextMenu contextMenu1;
		private System.Windows.Forms.MenuItem menuItem1;
		BaseTile currentTile = null;

		public Form1()
		{
			InitializeComponent();

			this.tiles = new BaseTile[2] {
                           new RectTile(new Rectangle(0, 0, 50, 50)),
                           new EllipseTile(new Rectangle(51, 0, 50, 50))};
		}

		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Vom Windows Form-Designer generierter Code
		/// <summary>
		/// Erforderliche Methode für die Designerunterstützung. 
		/// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
		/// </summary>
		private void InitializeComponent()
		{
			this.contextMenu1 = new System.Windows.Forms.ContextMenu();
			this.menuItem1 = new System.Windows.Forms.MenuItem();
			// 
			// contextMenu1
			// 
			this.contextMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {this.menuItem1});
			// 
			// menuItem1
			// 
			this.menuItem1.Index = 0;
			this.menuItem1.Text = "Action";
			this.menuItem1.Click += new System.EventHandler(this.menuItem1_Click);
			// 
			// Form1
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(292, 266);
			this.ContextMenu = this.contextMenu1;
			this.Name = "Form1";
			this.Text = "Form1";

		}
		#endregion

		/// <summary>
		/// Der Haupteinstiegspunkt für die Anwendung.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			Application.Run(new Form1());
		}

		protected override void OnMouseDown(MouseEventArgs e)
		{
			base.OnMouseDown (e);
			
			// get currentTile
			currentTile = null;

			for (int i = 0; i < tiles.Length; i++)
				if (tiles[i].Bounds.Contains(new Point(e.X, e.Y)))
					currentTile = tiles[i];
		}
		protected override void OnPaint(PaintEventArgs e)
		{
			base.OnPaint (e);
			for (int i = 0; i < tiles.Length; i++)
				tiles[i].Draw(e.Graphics);
		}


		private void menuItem1_Click(object sender, System.EventArgs e)
		{
			if (currentTile != null)
				currentTile.Action();
		}
	}

	class BaseTile
	{
		public Rectangle Bounds;
		
		public BaseTile(Rectangle bounds)
		{
			Bounds = bounds;
		}
		public virtual void Draw(Graphics g)
		{
			g.FillRectangle(Brushes.White, this.Bounds);
		}
		public virtual void Action()
		{
			MessageBox.Show(ToString());
		}
	}

	class RectTile : BaseTile
	{
		public RectTile(Rectangle bounds) : base(bounds)
		{
		}

		public override string ToString()
		{
			return "Ich bin ein RectTile";
		}
		public override void Draw(Graphics g)
		{
			base.Draw (g);
			g.DrawRectangle(new Pen(Color.Red, 2), 
				this.Bounds.X + 10,
				this.Bounds.Y + 10,
				this.Bounds.Width - 20,
				this.Bounds.Height - 20);
		}
	}
	class EllipseTile : BaseTile
	{
		public EllipseTile(Rectangle bounds) : base(bounds)
		{
		}
		public override string ToString()
		{
			return "Ich bin ein EllipseTile";
		}
		public override void Draw(Graphics g)
		{
			base.Draw (g);
			g.DrawEllipse(new Pen(Color.Red, 2), 
				this.Bounds.X + 10,
				this.Bounds.Y + 10,
				this.Bounds.Width - 20,
				this.Bounds.Height - 20);
		}
	}
}


P
939 Beiträge seit 2003
vor 19 Jahren

Ich würde beim Entwickeln des Spiele-Frameworks vom Objektmodell ausgehen, nicht von der GUI. Das Framework definiert seine eigene Klassenhierarchie, z.B. Schlumpf : Figur, Pilz : Gegenstand mit Aktionen und Ereignissen.
Beispiel: Ein Figur.GegenstandAufgenommen-Ereignis, wenn der Schlumpf den Pilz aufnimmt. Eine Kartenteil.Gegenstaende-Eigenschaft, aus der der Pilz entfernt wird und eine Figur.Inventar-Eigenschaft, wo er hinzugefügt wird.

Die GUI wird in eine eigene Schicht, ausserhalb des Frameworks, ausgelagert. So kann man sich immer noch überlegen, ob nun jedes Kartenteil ein eigenes Control ist oder alle Kartenteile von einem einzelnen Spielfeld-Control gerendert werden. Am Objektmodell ändert das nichts.

Gruss
Pulpapex

49.485 Beiträge seit 2005
vor 19 Jahren

Hallo Lynix,

nach den beiten Beiträgen von Xqgene und Pulpapex kann ich es mir einfach machen. Der Beitrag von Xqgene beantwortet Deine ursprüngliche GUI-bezogene Frage. Der Beitrag von Pulpapex beantwortet Deine aktuelle Frage:

Oder wie soll ich z.B. einer Kiste die auf einem bestimmten Feld steht eine Aktion zuweisen, wenn die Kiste kein Objekt ist sondern nur eine Graphik an einer Position innerhalb meines Spielfelds oder wegen mir Picturebox?

Um es mal frech zu sagen: Ordne die Aktionen einer Kiste immer der Kiste und nie der grafischen Repräsentation der Kiste zu. Um es noch mal klarer zu sagen: Was eine Kiste kann, gehört in die Modellklasse Kiste, wie und wo die Kiste als Grafik auf den Schirm kommt, gehört im ersten Ansatz die die GUI-Klasse Tile. Also auch wenn Dein erster Tile-Ansatz performancemäßig funktioniert hätte, hätten da die Kiste-Aktionen nicht reingehört! Wenn man nun im zweiten Ansatz auf Playground statt Tile ausweicht, ändert sich dadurch an den Modellobjekten (Kiste &Co) gar nichts.

Oder um es mit René Magritte zu sagen: "Das ist keine Pfeife" ("Ceci n` est pas un pipe"):

sondern das Abbild einer Pfeife. Unterscheide immer die Objekte und ihre grafische Repräsentation.

Wie man von Tile zu Playground kommt, hat ja Xqgene schon beschrieben.

HTH

herbivore

L
Lynix Themenstarter:in
667 Beiträge seit 2004
vor 19 Jahren

Vielen Dank @Xggene - damit wird mir etwas klarer wie Du/Ihr das gemeint habt.

Ich werds mal mit dem neuen Ansatz versuchen, nochmals Danke an alle !

"It is not wise to be wise" - Sun Tzu

99 Beiträge seit 2006
vor 17 Jahren

Ihr habt mir auch alle geholfen 😉
Ich bin gerade dabei auch paar graphische Entwicklertools für mich zu bastelen.

Von dem Thread [Tutorial] Zeichnen in Windows-Programmen (Paint/OnPaint, PictureBox) aus kam ich auf die Idee:
Zeichnen auf einer PictureBox und es geht bald weiter zu meinem graphischen Entwicklertool 😉

steel

1. Googlen 2. Boardsuche benutzen 3. Überlegen 4. Posten

Ich sage es nur äußerst ungerne,aber darf man in Foren/wo auch immer eine klitzekleine Frage stellen,ohne dass gleich ein Oberlehrer mit der obligatorischen "Google suchen"-Antwort kommt?

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo zusammen,

noch eine kleine Ergänzung: Es gibt für viele Controls passende Renderer-Klassen, also z.B. für einen Button die ButtonRenderer-Klasse. Damit kann man ein Control auf das Fenster zeichnen ([Artikel] Zeichnen in Windows-Programme), ohne dass es wirklich ein Control ist. Dadurch kann man sehr viele Controls darstellen, ohne die oben genannten Problem zu bekommen, die beim Verwenden von vielen echten Controls auftreten würden. Ein weiterer Vorteil gegenüber anderen Lösungen ist, dass es genauso aussieht, als wenn man echte Controls verwendet hätte. Der Nachteil ist, dass man den gezeichneten Controls selbst Leben einhauchen muss. Um im Beispiel zu bleiben: Beim einem Button müsste man selbst erkennen, an welche Stelle im Fenster geklickt wurde und welcher Button entsprechend im Zustand "gedrückt" dargestellt werden muss.

Ein Anwendungsbeispiel wäre Minesweeper. Dieses kleine Spiel zeigt - zumindest in den schwierigeren Stufen - so viele Buttons, dass es starke Performance-Probleme geben würde, wenn das alles echte Controls wären. Ein eigenes Minesweeper könnte man relativ aber leicht mit der ButtonRenderer-Klasse erstellen.

herbivore