Laden...

Forenbeiträge von Löwenherz Ingesamt 58 Beiträge

29.03.2007 - 07:25 Uhr

Hallo Herbivore,

das ist ganz bestimmt so, nur kann ich mir beim besten Willen nicht vorstellen, wie das gehen soll, da man keine Ableitungen von Delegate oder MulticastDelegate bilden und scheinbar auch nicht explizit auf diese Typen casten kann 😕

*verzweifelt*

Löwenherz

PS: Am liebsten wäre mir natürlich die Lösung mit der generischen Klassen, da man sich damit auch gleich Typsicherheit mit ins Haus holt - und ein Programmierer dann nichts mehr falsch machen kann 🙂

28.03.2007 - 16:52 Uhr

Hmmm, das werde ich mir morgen gleich als erstes mal anschauen 🙂

Wenn ich das mal rein theoretisch betrachte ... Dann müsste ich überall, wo ich ein Event asynchron abarbeiten will, ja diesen Aufwand mit foreach und BeginInvoke treiben 😉 Kann man das nicht etwas kapseln ? ^^

28.03.2007 - 13:31 Uhr

Danke schön für die Antworten 🙂

Ich habe mir allerdings schon das WMI Objekt "Win32_NetworkAdapter" angeguckt und eine Position auf dem PCI-Bus ist da leider nicht zu finden 😉

Löwenherz

28.03.2007 - 13:26 Uhr

Die "normalen" Events unter .NET werden ja sequentiell nacheinander abgearbeitet, und genau das will ich nicht - Ich möchte, dass die Clients des Events asynchron abgearbeitet werden. Und zu allem Überfluss muss das Ding auch noch mit Remoting funktionieren, aber da kümmere ich mich danach drum. Jetzt muss erstmal das asynchrone Event an sich funktionieren.

Löwenherz

28.03.2007 - 12:02 Uhr

Hallo Forum 🙂

Ich hätte mal wieder eine Frage an euch und hoffe, dass ihr vielleicht eine Lösung dafür auf Lager habt 🙂

Um es kurz zu machen:
Ich möchte mein Projekt mit Remoting aufziehen und stehe vor dem Problem, dass Remoting nur sehr rudimentäre Event-Unterstützung zu haben scheint. Das wesentliche Problem dürfte die sequentielle Abarbeitung von Aufrufen beim Feuern eines Events sein. Ich möchte die ganzen Aufrufe parallelisieren und jeden Aufruf von einem Threadpool durchführen lassen. Ich habe vor, eine generische Eventklasse zu bauen, die als Parameter den Delegaten übernimmt, der zur aufzurufenden Methode passt, so dass man ein Event einfach nur noch wie folgt erzeugen muss:

Event<MyEventHandler> m_event;

Mein Problem ist nun, wie kann ich das realisieren? 😉
Die bisherige Event-Klasse seht ihr unten. Mir fehlt vor allem folgendes:

  • die Einschränkung des Typs T auf Delegaten
  • und eine Methode Fire(), die als Argumente die Argumente des entsprechenden Delegaten hat

Das ist schon eine harte Nuss - zumindest weiß ich spontan nicht, wie ich das machen könnte bzw. ob das überhaupt geht, was ich vorhabe ^^


using System;
using System.Collections.Generic;
using System.Text;

namespace Test
{
  class Event<T>
  {
    private List<T> m_Listener;
    
    public void AttachListener(T listener)
    {
      m_Listener.Add(listener);
    }

    public void DetachListener(T listener)
    {
      m_Listener.Remove(listener);
    }
  }
}

Ich bedanke mich schon einmal im voraus bei euch 🙂

Viele Grüße,
Löwenherz

28.03.2007 - 09:46 Uhr

Hallo Borg,

es stimmt alles, was du sagst 🙂 Die Bilder sind JPEG-komprimiert und die Kameras werden über GBit-Ethernet angeschlossen. Die NICs selbst hängen an PCI Express. Die Bandbreite genügt schon, das ist kein Problem.

Die Kameras kommen mit einer einheitlichen Konfiguration aus dem Lager und die sollte auch niemand mehr anfassen müssen, um ihr eine passende IP-Adresse für Kamera 1 oder 2 zu verpassen. Ich möchte auf das Gehäuse schreiben können "Kamera 1" und "Kamera 2" und dann auch sicher sein, dass dies von der Software so verwendet wird. Mit Hilfe eines selbstgebauten Tools würde ich die passende NIC identifizieren wollen und dann der eine bestimmte IP verpassen. Danach könnte man sich vorstellen, dass man einen DHCP-Server für die Einrichtung der Kameras benutzt.

Für das Herausfinden der PCI-Location (Bus Number, Device Number, Function Number) habe ich allerdings noch keine passende Funktion gefunden. Hast du vielleicht eine Idee, wie ich da rankommen könnte?

Vielen Dank!

Löwenherz

27.03.2007 - 10:10 Uhr

Hallo 🙂

Irgendwie habe ich es scheinbar nicht geschafft, das auszudrücken, was ich meine ^^

Die Kameras und die PCs kommen alle ganz identisch aus dem Regal und haben daher alle die gleichen IP-Einstellungen. Die IP-Einstellungen sollen von der Software selbst gemacht werden, um reproduzierbare Ergebnisse zu erhalten. Um nun einer bestimmten NIC eine bestimmte Adresse zuordnen zu können, würde ich gerne wissen, wie man eine bestimmte NIC von anderen NICs im Rechner unterscheiden kann. Die Software soll diese Einstellungen auf allen baugleichen PCs identisch konfigurieren. Also, NIC1 soll z.b. 192.168.1.1 bekommen, NIC2 die 192.168.2.1 usw.

Ich will verhindern, dass durch das Abschalten oder Kaputtgehen einer NIC die Zuweisung "verrutscht". Die MAC-Adresse ist also nicht so gut geeignet, da sie ja logischerweise für jede NIC verschieden ist. Meine Idee war die Identifikation via PCI Bus Number, Device Number und Function Number, also die NICs in ihrer Position im PCI-Bussystem festzuzurren. Fällt nun eine aus, kann man über die Position im Bussystem immer noch eindeutig sagen, welche nun NIC1 ist und welche NIC2.

Die IPs werden kann mit einem eigenen Protokoll vergeben, das DHCP sehr ähnelt.

@GarlandGreene
Ich brauche schon eine NIC für jede Kamera, da da wirklich enorme Datenmengen zustande kommen, wenn man 1600x1200 RGB-Pixel bei ca. 30Hz transportiert.

26.03.2007 - 17:07 Uhr

Hallo und danke für deine schnelle Anwort!

Aber die MAC-Adresse eignet sich für diesen Zweck nicht so gut, da sie eben für jede NIC anders ist 🙂 Das, was ich suche, ist etwas, mit dem man zwei baugleiche Systeme "aufeinander abbilden" kann.

Nehmen wir mal das Beispiel mit den Kameras. Ich habe zwei Kamera: Kamera 1 und Kamera 2, beide hängen an jeweils einer eigenen Ethernetschnittstelle des PCs. Ich möchte vermeiden, dass die Software, die meine Bildauswertung macht, die Bilder von der falschen Kamera einzieht, wenn man den PC wechselt. Die PCs sind alle baugleich, so dass dies doch ohne weitteres möglich sein müsste...

Meine Sorge ist, dass das eine Windows NIC1 als erste Karte erkennt und das Windows auf dem anderen PC NIC2 als erste Netzwerkkarte nimmt. Gleiches Szenario, wenn NIC1 defekt sein sollte, würde NIC2 doch an dessen stelle rutschen, oder?

26.03.2007 - 13:51 Uhr

Hallo Community 🙂

Ich habe hier gerade zwei baugleiche PCs auf dem Tisch und möchte nun in meine Applikation eine Funktion einbauen, die sicherstellt, dass immer ein bestimmter Netzwerkanschluss angesprochen wird. Die beiden Kisten haben jeweils 3 Ethernet-Anschlüsse. Es sind wirklich drei Ethernet-Controller und nicht ein interner Switch 😉

Ich habe schonmal angedacht, das über die PCI Location zu machen, aber in der MSDN habe ich gelesen, dass die PCI Busnummer sich jederzeit ändern kann...

Habt ihr eine Idee, wie ich das Problem angehen könnte?

Zum Background: An jeden dieser Ethernet-Controller kommt genau eine Ethernet-Kamera dran und ich möchte sicherstellen, dass meine Software Kamera 1 auch immer an einem definierten Port findet und nicht mit einem mal Kamera 1 und Kamera 2 den "Platz tauschen" 😉

Ich bedanke mich schon einmal im voraus 🙂

Löwenherz

28.02.2007 - 09:31 Uhr

Da hast du sicherlich recht, werde ich mal probieren, danke dir!

27.02.2007 - 18:06 Uhr

Hallo Jack,

erst einmal ein Dankeschön für deine Antwort 🙂 Aber geht es nicht auch noch anders? Da die Spalten resizable sind, müsste ich bei jeder kleinen Änderung ein neues Bitmap erzeugen. Ist das nicht ein bisschen mit Kanonen auf Spatzen und so ... ? 😉

27.02.2007 - 15:26 Uhr

Hallo Leute 🙂

Ich habe in meinem Projekt ein DataGridView, das unter Umständen nur wenige Zeilen enthält. Die Standardhintergrundfarbe ist grau und meine Spalten sind farbig. Jede Spalte hat eine eigene Farbe. Ich möchte nun, dass das DataGridView die Spalten komplett - auch dort wo keine Zeilen mehr sind - in der jeweiligen Spaltenfarbe einfärbt.

Wenn das DataGridView nicht ganz gefüllt ist, sieht das nämlich ziemlich bescheiden aus, wenn unterhalb der gefüllten Zeilen die Spalte die Hintergrundfarbe des Controls hat 😉 Es gibt da bestimmt eine Möglichkeit, die Hintergrundfarbe spaltenweise anzupassen und ich hoffe nun, dass ihr mir da vielleicht weiterhelfen könnt 🙂

Vielen Dank schon einmal im voraus!

Löwenherz

27.07.2006 - 13:00 Uhr

Hm, die MSDN sagt folgendes dazu:

Bei einem verbindungsorientierten Socket liest die BeginReceive-Methode alle verfügbaren Daten bis zu der Anzahl der Bytes, die im size-Parameter angegeben wurde.

Daher sollte der Buffer eigentlich randvoll gemacht werden, bevor die Callback-Methode aufgerufen wird. Oder hast du vielleicht einen Empfangstimeout eingestellt ?

26.07.2006 - 19:04 Uhr

*lol*
Dann ist es ja kein Wunder, dass ich auch nichts gefunden habe g Aber ich steige demnächst um auf Visual Studio 2005 und dann gibt es auch .NET 2.0 mit der passenden Methode. Juchuu 🙂

Danke!

26.07.2006 - 19:02 Uhr

Hmm, was spricht dagegen, ein unvollständiges Paket zu puffern und mit dem nächsten Schwung an Daten zu interpretieren?

26.07.2006 - 14:55 Uhr

Der Puffer hat eine Länge von (mittlerweile) 4096 Byte.

Dann wird BeginReceive() versuchen, die 4096 Bytes auf vollzubekommen. Und das geht nicht, wenn du nur 9 bis 50 Bytes pro Datenstruktur, die du versendest, hast. Hast du mal probiert, die Anzahl der zu empfangenden Bytes auf die Größe eines Pakets zu reduzieren?

26.07.2006 - 08:36 Uhr

Hallo Leute 🙂

Ich knobel gerade an der Aufgabe rum, wie ich am besten die Abmessungen eines Forms speichern kann. Ich möchte einerseits den WindowState (Normal, Minimized, Maximized) speichern, als auch die Abmessungen des NICHT-maximierten Fensters, wenn das Fenster gerade im maximierten Modus ist. Rufe ich im Falle eines maximierten Fensters Control.Bounds() auf, so bekomme ich natürlich die maximierten Abmessungen, aber nicht die des normalen Fensters, das erscheint, wenn ich "maximiert" wieder wegnehme.

Bisher mache ich das so:


subkey.SetValue("Windowstate", (int) WindowState);
WindowState = FormWindowState.Normal;
subkey.SetValue("Bounds (x)", Bounds.X);
subkey.SetValue("Bounds (y)", Bounds.Y);
subkey.SetValue("Bounds (cx)", Bounds.Width);
subkey.SetValue("Bounds (cy)", Bounds.Height);

Der Wermutstropfen ist nun aber, dass das Fenster sich erstmal sichtbar in den Normal-Modus fährt, bevor es sich schließt... und das ist irgendwie unnötig.

Wie komme ich denn sonst noch an die normale Fenstergröße eines maximierten Forms oder allgemein Controls?

Vielen Dank!

23.07.2006 - 10:29 Uhr

Hm, tun die Ereignisse (Messages) ja auch nicht, die werden halt gequeued und später abgearbeitet. Irgendwie ist das ein toller Fall mit Laufzeitproblemen, für den man nur einen Thread haben muss 😁

22.07.2006 - 12:15 Uhr

Achso, mir fällt da noch was zur Ergänzung ein...

Ersetze doch mal den MessageBox.Show()-Aufruf durch einen Aufruf von Form.ShowDialog(). Das gibt das gleiche Problem. Der Witz ist irgendwie die Implementierung von modalen Dialogfenstern in .NET.

Wenn du die besagte Zeile aber durch ein Thread.Sleep() oder ein AutoResetEvent (z.B. m_event.WaitOnE(1000, false)) ersetzt. Dann blockiert der GUI-Thread wirklich und es ist alles so wie ich es erwarten würde. Nur diese bekloppten modalen Forms machen da echt Ärger... warum auch immer 😉

Herbivore hat in einem anderen Thread hier auch schonmal darauf hingewiesen, dass MessageBoxes beim Debuggen das allerletzte sind. Ich glaube, ich weiß jetzt auch, wieso 😉

22.07.2006 - 12:08 Uhr

Hmm, ok 🙂

Ich glaube, dann habe ich es verstanden - glaube ich. Danke dir!

Auch wenn in den CodeProject-Artikel steht, dass es eine Messageloop intern gibt, die die PostMessage-Messages abarbeitet. Und solange der Handler einer dieser Messages noch nicht vollständig durchgelaufen ist, sollte die nächste warten müssen. Daher kam so das Problem auf ^^

21.07.2006 - 11:33 Uhr

Hallo nochmal und sorry, dass ich euch erneut behelligen muss 😉

Schaut euch mal bitte das folgende kleine Testprogramm an. Es reproduziert genau den Fehler, den ich meinte.


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

namespace TestApp
{
	/// <summary>
	/// Zusammenfassung für Form1.
	/// </summary>
	public class Form1 : System.Windows.Forms.Form
	{
		/// <summary>
		/// Erforderliche Designervariable.
		/// </summary>
		private System.ComponentModel.Container components = null;
    private Thread thread1;
    private Thread thread2;
    private Thread thread3;
    private System.Windows.Forms.Button m_ButtonStart;

		public Form1()
		{
			InitializeComponent();
      Thread.CurrentThread.Name = "GUI Thread";
		}

		/// <summary>
		/// Die verwendeten Ressourcen bereinigen.
		/// </summary>
		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.m_ButtonStart = new System.Windows.Forms.Button();
      this.SuspendLayout();
      // 
      // m_ButtonStart
      // 
      this.m_ButtonStart.Location = new System.Drawing.Point(96, 112);
      this.m_ButtonStart.Name = "m_ButtonStart";
      this.m_ButtonStart.TabIndex = 0;
      this.m_ButtonStart.Text = "Start";
      this.m_ButtonStart.Click += new System.EventHandler(this.m_ButtonStart_Click);
      // 
      // Form1
      // 
      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
      this.ClientSize = new System.Drawing.Size(292, 266);
      this.Controls.Add(this.m_ButtonStart);
      this.Name = "Form1";
      this.Text = "Form1";
      this.ResumeLayout(false);

    }
		#endregion

    private delegate void VoidDelegate();

    private void ThreadProc1()
    {
      Thread.CurrentThread.Name = "Thread 1";
      this.Invoke(new VoidDelegate(Print));
    }

    private void ThreadProc2()
    {
      Thread.CurrentThread.Name = "Thread 2";
      this.Invoke(new VoidDelegate(Print));
    }

    private void ThreadProc3()
    {
      Thread.CurrentThread.Name = "Thread 3";
      this.Invoke(new VoidDelegate(Print));
    }

    private void Print()
    {
      Debug.WriteLine("--- Print started: " + Thread.CurrentThread.Name + " ---");
      for(int i=0; i < 15; i++) {
        Debug.WriteLine("Line " + i);
        if (i == 10) MessageBox.Show("Es ist die 10. Runde!");
      }
      Debug.WriteLine("--- Print stopped: " + Thread.CurrentThread.Name + " ---");
    }

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

    private void m_ButtonStart_Click(object sender, System.EventArgs e)
    {
      thread1 = new Thread(new ThreadStart(ThreadProc1));
      thread2 = new Thread(new ThreadStart(ThreadProc2));
      thread3 = new Thread(new ThreadStart(ThreadProc3));
      thread1.Start();
      thread2.Start();
      thread3.Start();
    }
	}
}

Im Log steht dann folgendes:


--- Print started: GUI Thread ---
Line 0
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
--- Print started: GUI Thread ---
Line 0
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
--- Print started: GUI Thread ---
Line 0
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
Line 11
Line 12
Line 13
Line 14
--- Print stopped: GUI Thread ---
Der Thread 'Thread 2' (0xa98) hat mit Code 0 (0x0) geendet.
Line 11
Line 12
Line 13
Line 14
--- Print stopped: GUI Thread ---
Der Thread 'Thread 3' (0xbf4) hat mit Code 0 (0x0) geendet.
Line 11
Line 12
Line 13
Line 14
--- Print stopped: GUI Thread ---
Der Thread 'Thread 1' (0x29c) hat mit Code 0 (0x0) geendet.

Noch während ich mich mit meinem GUI-Thread in der Ausführung von Print() befinde, wechselt die Kontrolle und der GUI-Thread beginnt die Print()-Methode erneut. Wie kann das denn sein, wenn Control.Invoke() und Control.BeginInvoke() angeblich nichts weiter tun, als eine Message in die Messagequeue des Steuerelements einzuhängen?!

Ich werde daraus echt nicht schlau, sorry 😦

Aber vielleicht kann mir ja eine(r) von euch sagen, wo ich da den Knoten im Denkapparat habe 😁

16.07.2006 - 21:49 Uhr

😁

Eigentlich gar nichts. Wir haben es offenbar nur geschafft aneinander vorbei zu diskutieren ^^

Und wie du schon sagst, es wird immer der GUI-Thread arbeiten. Der Witz ist aber, dass in meiner Applikation (scheinbar) im Kontext des GUI-Threads die aufgerufene Methode angefangen, unterbrochen, vom anderen Thread gestartet, wieder unterbrochen und dann zuende gelaufen ist. Das Problem ist bei mir der Wechsel des Kontextes während der Ausführung der Methode, obwohl ich BeginInvoke benutzt habe.

Die Grundlagen und wie BeginInvoke funktionieren sollte sind mir klar, aber das resultierende Verhalten (s.o.) hat mich echt fertig gemacht ^^ Ich glaube, ich schaue mir das morgen nochmal gaaaaaanz genau an. Kann ja nicht so schwer sein 😉 Ich habe sicherlich nur irgendwas übersehen.

Ich danke euch auf jeden Fall für die vielen Antworten! 😒hakehands:

16.07.2006 - 20:32 Uhr

Original von Traumzauberbaum
Du verstehst da glaub ich Synchronisation falsch.

Die Nachrichten werden in den GUI Thread auch genau in der Reihenfolge ausgewertet wie du sie reinsteckst. Nur hat das mit Synchronisation herzlich wenig zu tun.

Genau das passiert bei dir auch.

Synchronisation heißt grob gesagt, dass ein Thread auf einen anderen wartet.

Und genau das ist das Problem bei dir.

Das Geschehen läuft in 2 Threads ab, keiner wartet auf den anderen.
Der eine startet ohne auf das Beenden des Anderen zu warten. Also kommt der Aufruf zum Start des 2. vor dem beenden des 1. Also kommt auch in die MessageQueue erst die 2. Meldung vom Start, bevor einer das Beenden meldet.

Kannst dir doch mal die Uhrzeit/Tickcount anschauen, bei denen jeweils Anfang und Ende gemeldet werden.

Joa, das ist alles richtig, sehe ich auch so. Aber das betrifft eher 2 Threads, die "einfach so" (ohne Messagequeue) miteinander interagieren. Wenn ich eine Messagequeue habe, muss der aufrufende Thread doch eigentlich nur noch eine Message in die Queue stecken und die aufzurufende Methode wird dann ausgeführt, wenn der GUI-Thread die Message abholt und dann abarbeitet. Hmmm 😕

16.07.2006 - 20:20 Uhr

Original von MarsStein
@Löwenherz
Wenn Du denvon Dir geposteten Artikel bei codeproject richtig gelesen hast, müsste Dein Problemeigentlich klar geworden sein. Ich halte den Artikel auch für sehr gut. Du scheinst an dieser Stelle noch ein grundsätzliches Verständnisproblem zu haben, lies den Artikel nochmal genau durch. Dann siehst Du, daß Du in DeinemFall entweder mit Invoke() arbeitenen solltest, oder Deine Threads eben manuell synchronisieren musst.

Sorry, ich habe nicht richtig geantwortet...
Ich habe den Artikel aufmerksam gelesen und der besagte genau das, was ich auch angenommen habe.

Mal eine Frage ... Was sollte BeginInvoke() denn sonst bringen, wenn nicht die Kontrolle an einen anderen Thread zu übergeben? Und da der GUI-Thread ja intern messagebasiert arbeitet, führt das mE zwangsweise zu einer Synchronisation.

16.07.2006 - 19:52 Uhr

Joa, das kann gut sein. Ich habe eben noch ein bissl mit dem Problem experimentiert.
Folgender Code läuft bei mir wie gewünscht ab, und zwar so, dass die Print()-Methode nicht unterbrochen wird und sauber von Anfang bei Ende durchläuft, bevor sie eine neue Runde dreht und die Anforderung vom anderen Thread bedient. verzweifel


using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace BeginInvokeTest
{
  class Program : Form
  {
    private Thread thread1;
    private Thread thread2;

    Program()
    {
      Thread.CurrentThread.Name = "GUI Thread";
      thread1 = new Thread(new ThreadStart(ThreadProc1));
      thread2 = new Thread(new ThreadStart(ThreadProc2));
      thread1.Start();
      thread2.Start();
    }

    ~Program()
    {
      thread1.Join();
      thread2.Join();
    }

    private delegate void PrintDelegate(string s);

    private void ThreadProc1()
    {
      Thread.CurrentThread.Name = "ThreadProc1";
      while (true)
      {
        if (this.Created) this.BeginInvoke(new PrintDelegate(Print), new object[] { "Ich bin ThreadProc1" });
        Thread.Sleep(500);
      }
    }

    private void ThreadProc2()
    {
      Thread.CurrentThread.Name = "ThreadProc2";
      while (true)
      {
        if (this.Created) this.BeginInvoke(new PrintDelegate(Print), new object[] { "Ich bin ThreadProc2" });
        Thread.Sleep(500);
      }
    }

    void Print(string s)
    {
      Console.WriteLine(Thread.CurrentThread.Name + " [Anfang] - " + s);
      for (int i = 0; i < 10; i++) {
        Console.WriteLine(Thread.CurrentThread.Name + " - " + i);
        Thread.Sleep(0);
      }
      Console.WriteLine(Thread.CurrentThread.Name + " [Ende] - " + s);
    }


    static void Main(string[] args)
    {
      Program prog = new Program();
      Application.Run(prog);
    }
  }
}

Und das bildet eigentlich genau mein Problem nach, was ich mit Socket.BeginConnect und anschließendem Control.BeginInvoke zu haben scheine g Oh Mann 😉

16.07.2006 - 19:16 Uhr

Original von MarsStein
Hallo,

nur leider war die ganze Geschichte dann nicht mehr synchron
wie denn auch? Du hast es ja explizit asynchron aufgerufen!! Wenn Du das wieder synchron habe willst, verweise ich nochmal auf die Verwendung von lock (siehe mein letzter Post).
und ich weiß leider nicht, wie ich die Thread-ID rausbekomme
mit Thread.CurrentThread kommst Du immer an den gerade laufenden Thread.

Hmm, ja ^^

Die Asynchronität sollte meines Erachtens nach beim Control.BeginInvoke() enden, da das ja eine Window-Message in die Messagequeue des GUI-Threads postet. Das versuche ich ja die ganze Zeit klarzumachen. So steht es zumindest bei
http://www.codeproject.com/csharp/begininvoke.asp

Und mit Thread.CurrentThread hast du schon recht, aber da komme ich höchstens an einen Namen mittels Thread.Name. Eine ID gibt es leider nicht. Wenn du mir aber sagen kannst, wie ich einem Thread aus dem Threadpool einen Namen zuordne, wäre ich aus dem Schneider 🙂

16.07.2006 - 10:11 Uhr

Die asynchronen Aufrufe mit BeginConnect() habe ich genommen, damit mir mein GUI-Thread nicht blockiert, wenn er keinen Connect hinbekommt und irgendwann mit einem Timeout wiederkommt. Die Applikation sieht dann ziemlich abgestürzt aus, was sie aber nicht ist.

Und das Control.BeginInvoke() wollte ich eigentlich nur mal ausprobieren und war verwundert, wieso es nicht ging 😁 Verstanden habe ich das mit Problem mit Control.BeginInvoke() leider immer noch nicht.

Ich habe mir das Ding hier mal durchgelesen:
http://www.codeproject.com/csharp/begininvoke.asp

... und musste feststellen, dass die Callback-Methoden der Socket.BeginConnect()-Methode mit Control.BeginInvoke() im GUI-Thread per Window-Message die gewünschte Methode anstoßen sollten. Haben sie ja auch, nur leider war die ganze Geschichte dann nicht mehr synchron und ich weiß leider nicht, wie ich die Thread-ID rausbekomme, sonst hätte ich da einfach mal ein Debug.WriteLine() mit der Thread-ID reingebastelt, um rauszubekommen, wer denn da meine GUI-Methode aufruft ^^

15.07.2006 - 12:43 Uhr

Ja, ist leider nötig, da ich meinen GUI-Thread unter keinen Umständen blockieren darf.

Und unübersichtlich finde ich das auch irgendwie nicht. Eigentlich ist es doch ganz einfach. Ich verstehe nur nicht, wieso das nicht so funktioniert g

  • BeginConnect() startet einen Thread aus dem Threadpool und lässt die Connect-Operation darin laufen
  • Callback-Methode wird von diesem Thread aufgerufen
  • Innerhalb der Callback-Methode packe ich mit BeginInvoke() eine Message in die Messagequeue von meinem GUI-Thread
  • GUI-Thread arbeitet die Message irgendwann ab, wenn sie dran ist.

Das sieht für mich doch nach einer definierten Reihenfolge aus. Durch das Posten einer Message in BeginInvoke() sequentialisiert sich doch die gesamte Ausführung. Zumindest meinem Verständnis nach 😉

Bitte korrigiert mich, wenn ich da falsch liege.

14.07.2006 - 21:16 Uhr

Erst einmal vielen Dank für eure Antworten!

Original von MarsStein
Hallo,
wen ich das jetzt richtig verstanden habe passiert folgendes:

Ich starte mit Socket.BeginConnect() zwei Verbindungsversuche
Durch den asynchronén Connect-Aufruf werden 2 Threads gestartet, die jeweils versuchen sich zu verbinden.
Jeder dieser Threads ruft dann Dein Callback-Funktion. In dieser wird wiederum mit "BeginInvoke" ein asynchroner Aufruf gestartet, so daß die Methode, die über BeginInvoke ausgeführt wird, zwiemal gerufen wird.
Da aber bereits das BeginConnect asynchron ausgeführt wird, kommt es vermutlich zu folgender (oder ähnlicher) Reihenfolge bei der Ausführung

BeginConnect1
Callbackaufruf1 -> ruft BeginInvoke -> Ausgabe ANFANG
BeginConnect2
Callbackaufruf2 -> ruft BeginInvoke -> Ausgabe ANFANG
Ende Invoke1 -> Ausgabe ENDE
EndeCallback1
Ende Invoke2 -> Ausgabe ENDE
EndeCallback2

Musst Du dich eigentlich gleich 2mal connecten oder könntest du evtl. die beiden Connect synchron hintereinander ausführen?

Hmm, so habe ich das anfangs auch gedacht. Aber wie svenson auch geschrieben hat, wird die Ausführung dieser beiden aufgerufenen Delegaten im GUI Thread nacheinander ausgeführt. Daher müsste meines Erachtens nach doch zuerst der erste Aufruf im GUI-Thread komplett durchlaufen, bevor der zweite Aufruf abgearbeitet werden kann. Ich habe gelesen, dass BeginInvoke und Invoke mit der API-Funktion PostMessage() implementiert sind und daher ja auch nacheinander ausgeführt werden müssten. Aber genau das scheint nicht zu klappen. Sehr seltsam ... nachdenklich am Kopf kratz

14.07.2006 - 08:26 Uhr

Hallo Leute 🙂

Ich habe da ein ganz besonderes Leckerli, an dem ich schon ein paar Stunden rumlaboriere ^^ Ich starte mit Socket.BeginConnect() zwei Verbindungsversuche zu einem Host im Netzwerk. Die Callback-Methode, die bei Erfolg oder Misserfolg aufgerufen wird, läuft in einem Thread des Threadpools, wenn ich das recht verstanden habe. Da meine GUI aber auch etwas davon mitbekommen soll, habe ich in dieser Callback-Methode Control.BeginInvoke() benutzt, um meinen GUI-Thread über den Ausgang des Verbindungsaufbaus zu informieren.

An Anfang und Ende dieser Methode, die im Context des GUI-Threads läuft, habe ich eine Meldung eingebaut, die dann ins Log geschrieben wird. Lustigerweise kommt folgendes dabei raus:

ANFANG
ANFANG
ENDE
ENDE

d.h. der GUI-Thread unterbricht irgendwo in der Mitte der Methode die Ausführung und "beginnt von vorn". Irgendwann läuft die erste Ausführung dann weiter. Das ist doch völlig unmöglich, wenn ich Control.BeginInvoke() zum Aufruf dieser GUI-Methode benutze, oder?

Nebenbei... Mit Constrol.Invoke(), statt mit control.BeginInvoke() läuft die Kiste... seeeehr seltsam.

Ich hoffe, jemand von euch hatte so ein Problem schon einmal und kann mir sagen, woran es liegt.

Vielen Dank schon einmal im vorraus!

31.05.2006 - 10:29 Uhr

Fast 😉 Ich probiere es gleich mal aus. Im Moment kämpfe ich ein bisschen mit dem Refresh rum. Ich will ja nur zeichnen, wenn es nötig ist... sprich: in OnPaint().

Starte ich die Applikation ist das Fenster erstmal schwarz, den Inhalt pinselt die OnPaint()-Methode erst rein, wenn der Clientbereich des Fensters auf irgendeine Weise verdeckt und wieder "freigegeben" wird. Das Witzige daran ist, dass OnPaint() auch am Anfang sogar mehrfach läuft und das Bild erzeugen müsste, doch nichts passiert 🙁 verzweifel

31.05.2006 - 08:42 Uhr

Sorry für das dritte Posting ^^

Ich habe nun eine Lösung gebastelt, allerdings stört mich der Umweg über die GDI. Die GDI hat ja nicht gerade den Ruf, sehr performant zu arbeiten. Ich suche daher immer noch eine bessere und evtl. gradlinigere Möglichkeit, das Bitmap anzuzeigen.


public void Render()
{
  m_Device.Clear(ClearFlags.Target, Color.Blue, 0.0f, 0);
  Bitmap bitmap = new Bitmap(@"c:\Fächer.bmp");
  Surface backbuffer = m_Device.GetBackBuffer(0,0,BackBufferType.Mono);
  
  Graphics grfx = backbuffer.GetGraphics();
  grfx.DrawImage(bitmap, 0, 0);
  backbuffer.ReleaseGraphics();

  m_Device.Present();
}

31.05.2006 - 07:42 Uhr

Hmm, ok ... ich habe es nun (scheinbar) hinbekommen, ein Bitmap in eine Surface zu laden.

Bitmap bitmap = new Bitmap(@"c:\Fächer.bmp");
Surface surface = Surface.FromBitmap(m_Device, bitmap, Pool.Default);

Doch wie bekomme ich die nun angezeigt auf dem Bildschirm zu sehen? 😦

Ich bin über jeden Tipp, der mir den weiteren Weg aufzeigt, sehr dankbar 🙂

30.05.2006 - 13:42 Uhr

Hallo Leute 🙂

eigentlich möchte ich nur ein Bitmap mit Hilfe von Direct3D anzeigen. Ich habe gelesen, dass man die Bitmap dafür auf ein Surface bringen muss. Allerdings habe ich das bisher nicht hinbekommen. Könnte mit jemand kurz sagen, wie ich das hinkriegen kann?

Vielen Dank!

Btw. das Direct3D-Tutorial ist super 😁[Artikel] Managed Direct3D Tutorial

19.05.2006 - 12:54 Uhr

Alles klar. Danke dir! 🙂

18.05.2006 - 18:40 Uhr

Ich verstehe. Gibt es vielleicht eine andere Methode, mit der man garantieren kann, dass eine bestimmte Zeile ganz oben in der Multiline-TextBox angezeigt wird?

18.05.2006 - 15:51 Uhr

Hallo Herbi,

das sollte doch auch mit einer TextBox funktionieren, oder?

Ich kann mich da echt kopfstellen, dieses blöde Ding will beim Aufruf der folgenden Zeilen einfach nicht das Caret versetzen ...


m_TextBoxMessages.SelectionStart = 10;
m_TextBoxMessages.ScrollToCaret();

Und ja, die Box hat den Stil "multiline" 🙂

14.05.2006 - 09:24 Uhr

Hallo Kostas,

danke für den Tipp. Es funktioniert jetzt auch bei mir 🙂

13.05.2006 - 18:25 Uhr

Achso,
ein eine Frage ist doch noch offen ^^

Ist Socket.Send() und Socket.Receive() zeitgleich von zwei verschiedenen Threads aus nutzbar, oder gibt es da Synchronisationsprobleme?

13.05.2006 - 17:11 Uhr

Hallo Leute 🙂

Ich bastele gerade an einem Interpreter rum, der Pakete an ein DSP-Board via Ethernet senden und diese auch empfangen soll. Mein Problem ist ganz knapp gefasst, dass die Applikation scheinbar ein Synchronisationsproblem hat. Manchmal läuft es korrekt ab, aber meistens geht es daneben.

Eine kurze Beschreibung des Umfeld...
Ich verwende ein TcpClient-Objekt, das die Verbindung zum DSP-Board aufbaut, und einen Thread, der stetig blockierend NetworkStream.Read() ausführt und damit asynchron zum Rest der Applikation auf Daten von dem DSP wartet. Manchmal muss ich aber eine Anfrage an den DSP schicken. Das mache ich im Kontext der Applikation mit NetworkStream.Write(). Und genau hier scheint irgendwas durcheinander zu gehen. Es kommt zu einem nicht mehr vorhersagbarem Verhalten.

Hat jemand sowas schonmal gemacht und ist evtl. auf das gleiche Problem gestoßen? Ich würde mich echt riesig über jeden Hinweis, der wenigstens die Richtung der Problemlösung aufzeigt, freuen 🙂

Ich danke euch schonmal im vorraus 🙂

24.04.2006 - 08:32 Uhr

Oha, das sah anfangs ganz gut aus. Aber der Interpolationsmodus NearestNeighbor hat einen dicken Nachteil. Er interpoliert immer noch g

Ich brauchte aber einfach eine Verdopplung, Verdreifachung usw. der Pixel. Andernfalls haut es mit meiner Koordinatenberechnung nicht hin. Ergebnis war nun, dass ich dafür eine Methode geschrieben habe, die eine entsprechend gezoomte Bitmap erzeugt und die dann ausgegeben hat.

23.04.2006 - 18:39 Uhr

Danke Jan!

Wie ich nun aber so bin, war ich mit der Standardlösung nicht so recht zufrieden. Meine kleine Applikation in Kürze: Ich habe ein Form, dass schon einige Steuerelemente enthält und habe es nun in ein Mdi-Container verwandelt, damit ich darin mit Toolfenstern rumhantieren kann, ohne, dass die Toolfenster des Applikationsform verlassen können. So weit, so gut...

Mein Problem ist nur, dass sich die Steuerelemente im MdiContainer immer über die MDI-Clients legen. Das ist echt ätzend ^^

Weiß da jemand einen Ausweg, wie ich die MDI-Clients so anzeigen kann, dass sie nicht von Steuerelementen verdeckt werden?

Vielen Dank!

23.04.2006 - 09:21 Uhr

Ahhhh, jetzt weiß ich, was du meintest mit

m_ClientPanel.ClientSize wird benutzt bevor m_ClientPanel.Size gesetzt wird. Kommt mir seltsam vor.

Die korrigierte Methode sieht etwas anders aus 😉


    protected void RefreshControls()
    {
      // dock horizontal scrollbar and calculate new panel width
      Size size = new Size(0,0);
      m_hScrollBar.Location = new Point(0, ClientSize.Height - m_hScrollBar.Height);
      if (WillGetVerticalScrollbar()) {
        m_hScrollBar.Width = ClientSize.Width - m_vScrollBar.Width;
        size.Width = m_vScrollBar.Left;
      } else {
        m_hScrollBar.Width = ClientSize.Width;
        size.Width = ClientSize.Width;
      }

      // dock vertical scrollbar and calculate new panel height
      m_vScrollBar.Location = new Point(ClientSize.Width - m_vScrollBar.Width, 0);
      if (WillGetHorizontalScrollbar()) {
        m_vScrollBar.Height = ClientSize.Height - m_hScrollBar.Height;
        size.Height = m_hScrollBar.Top;
      } else {
        m_vScrollBar.Height = ClientSize.Height;
        size.Height = ClientSize.Height;
      }

      // set new panel size
      m_ClientPanel.Size = size;

      // horizontal scrollbar
      if (WillGetHorizontalScrollbar()) 
      {
        m_hScrollBar.Minimum = 0;
        m_hScrollBar.Maximum = PanelClientSize.Width - 1;
        m_hScrollBar.SmallChange = 10;
        m_hScrollBar.LargeChange = m_ClientPanel.ClientSize.Width;
        m_hScrollBar.Show();
      } 
      else 
      {
        m_hScrollBar.Minimum = 0;
        m_hScrollBar.Maximum = 0;
        m_hScrollBar.Hide();
      }

      if (WillGetVerticalScrollbar()) 
      {
        m_vScrollBar.Minimum = 0;
        m_vScrollBar.Maximum = PanelClientSize.Height - 1;
        m_vScrollBar.SmallChange = 10;
        m_vScrollBar.LargeChange = m_ClientPanel.ClientSize.Height;
        m_vScrollBar.Show(); 
      } 
      else 
      {
        m_vScrollBar.Maximum = 0;
        m_vScrollBar.Minimum = 0;
        m_vScrollBar.Hide();
      }

      // adjust horizontal scrollbar value to new range
      int nMax = m_hScrollBar.Maximum - m_hScrollBar.LargeChange + 1;
      if (nMax > 0) {
        if (m_hScrollBar.Value > nMax) {
          m_hScrollBar.Value = nMax;
        } 
      } else {
        m_hScrollBar.Value = 0;
      }
      
      // adjust vertical scrollbar value to new range
      nMax = m_vScrollBar.Maximum - m_vScrollBar.LargeChange + 1;
      if (nMax > 0) {
        if (m_vScrollBar.Value > nMax) {
          m_vScrollBar.Value = nMax;
        } 
      } else {
        m_vScrollBar.Value = 0;
      }
    }

23.04.2006 - 08:46 Uhr

Resize sollte scrollbar.Value nicht automatisch auf 0 setzen

Das betreffende Code-Fragment:


      // adjust horizontal scrollbar value to new range
      int nMax = m_hScrollBar.Maximum - m_hScrollBar.LargeChange - 1;
      if (nMax > 0) {
        if (m_hScrollBar.Value > nMax) {
          m_hScrollBar.Value = nMax;
        } 
      } else {
        m_hScrollBar.Value = 0;
      }

Ich will damit nachschauen, ob der aktuelle Wert der ScrollBar über oder unter dem Maximum der ScrollBar liegt. Wenn man Minimum und Maximum ändert, wird ja nicht automatisch der Wert angepasst. So kann es dazu kommen, dass der Wert einer ScrollBar nach Änderung von Minimum und Maximum außerhalb der zulässigen Grenzen liegt. Daher diese kleine Deckelung nach oben hin. Ich hoffe, ich konnte das verständlich darlegen 🙂

m_ClientPanel.ClientSize wird benutzt bevor m_ClientPanel.Size gesetzt wird. Kommt mir seltsam vor. Überhaupt ist mir der Sinn von "recalculate panel size" nicht klar.

Hmm, gib mir mal bitte einen Tipp, wo ich m_ClientPanel.ClientSize nutze, bevor es gesetzt wird. Ich habe irgendwie gerade Tomaten auf den Augen. Der Abschnitt "recalculate panel size" sorgt dafür, dass je nach Sichtbarkeit der ScrollBars die Breite/Höhe der jeweils anderen Scrollbar angepasst wird. Mein Ziel war, dass die horizontale Scrollbar die die gesamte Breite des Fensters einnimmt, sofern sie gebraucht wird und es keine vertikale ScrollBar gibt. Gibt es aber eine horizontale und eine vertikale ScrollBar, so stellen sich die ScrollBars "über Eck". Es entsteht dann ein kleiner rechteckiger "Hohlraum" zwischen dem Down-Button der vertikalen und dem Right-Button der horizontalen ScrollBar.


     // recalculate panel size
      Size size = new Size(0,0);
      if (m_hScrollBar.Visible) {
        size.Height = m_hScrollBar.Top;
      } else {
        size.Height = ClientSize.Height;
      }

      if (m_vScrollBar.Visible) {
        size.Width = m_vScrollBar.Left;
      } else {
        size.Width = ClientSize.Width;
      }

Warum benutzt du nicht die "automatic docking properties" (falls es die gibt)?

Die gibt es, allerdings entsteht dann nicht der eben angesprochene Hohlraum zwischen den ScrollBars. Beim Docking nimmt eine ScrollBar immer die ganze Höhe bzw. Breite ein. Das sah in meinen Augen furchtbar aus 😁

Ich bin mir nicht sicher, ob die "- 1" hier stimmen:
nMax = m_vScrollBar.Maximum - m_vScrollBar.LargeChange - 1;
nMax = m_hScrollBar.Maximum - m_hScrollBar.LargeChange - 1;

*grübel* Ich glaube, du hast recht, die ist wohl falsch. Müsste +1 sein, oder?
Mal laut denken: Wenn ich einen Bereich von 800 Pixeln habe (Minimum stelle ich dann auf 0 und Maximum auf 799 ein, LargeChange bekommt den Wert 400). Dann habe ich zwei gleichgroße Scrollbereiche. Der erste würde von 0 bis 399 gehen und der zweite von 400 bis 799. Der größte Scrollwert ist dann ja 400. Auf 400 komme ich auch, wenn ich Maximum (799) - LargeChange(400) + 1 rechne. Oder habe ich da einen grundsätzlichen Denkfehler drin?

Ich habe den Quelltext oben bereits angepasst. Steht nun auch +1 drin.

Bonus (optional): Was ist der Unterschied zwischen bzw. die Definition von

  • PanelClientSize
  • m_ClientPanel.Size
  • m_ClientPanel.ClientSize
  • ClientSize

PanelClientSize ist abstract und wird von der abgeleiteten Klasse implementiert und gibt an, wie groß der virtuelle Clientbereich sein soll. Wenn ich ein Bild in das Steuerelement reinpacken will, dann gibt diese Property die Breite und Höhe des Bildes an, damit die ScrollBars sich so einstellen können, dass auch das ganze Bild "angescrollt" werden kann.

m_ClientPanel.Size gibt an, wie groß das Steuerelement inklusive Rahmen ist. Ich habe einen dünnen Rahmen um das Element gezogen, der gehört zum Steuerelement dazu, ist aber nicht Clientbereich.

m_ClientPanel.ClientSize gibt die für Painting nutzbare Fläche im Panel an.

ClientSize oder besser this.ClientSize gibt die Größe der für Painting nutzbaren Fläche an. Da die ScrollBars in der Klasse als normale Steuerelemente eingebaut sind und nicht vom Fenster bereitgestellt werden, werden die ScrollBars wie andere Steuerelemente behandelt und im Clientbereich des Fensters angelegt. Das verhält sich anders bei AutoScrollbars, die den Clientbereich verkleinern, wenn sie aktiviert werden.

Ich hoffe, das konnte ein bisschen meine Ideen, die ich so bei der Implementierung hatte, erklären 🙂

Falls ich irgendwo Unfug erzählt haben sollte, bitte melden!

22.04.2006 - 23:44 Uhr

Hmmm, wie wäre es mit GPRS und WAP ? ^^
oder ... eine ganz simple TCP-Verbindung über das Internet? Internetfähige Handys gibt es ja wie Sand am Meer, aber wie das mit PDAs ist, weiß ich leider nicht.

22.04.2006 - 23:28 Uhr

Original von Birgit

Bleibt mir nur noch, den Barcode abzufotografieren. 🙂

Stimmt leider. Aber die Data Matrix Codes sind, wenn man sie nicht mit Informationen vollstopft recht fehlertolerant. Da steckt so viel Redundanz drin, dass man bei einigen mit einem Edding drin rummalen kann, ohne dass was verloren geht. Ich habe das mal ausprobiert ^^

Was ich damit sagen wollte, ist ... So eine kleine PDA-Kamera macht sicherlich ein ausreichend gutes Bild, dass du damit den Data Matrix Code "einscannen" kannst.

22.04.2006 - 22:58 Uhr

Hallo Leute,

ich habe mich in letzter Zeit intensiv mit dem Problem auseinandergesetzt, ein eigenes Steuerelement zu schreiben, das dynamisch seinen Clientbereich vergrößern und verkleinern kann. Entsprechend der "virtuellen" Clientgröße, sollten Scrollbars nur bei Bedarf angezeigt oder ausgeblendet werden. Das Problem kam hier im Forum auch schon mehrfach auf und wurde meist mit Autoscrollbars gelöst. Aber genau mit denen hatte ich so meine liebe Müh 🙂.

Kurzum, ich habe ein UserControl genommen, zwei Scrollbars und ein Panel reingesetzt und alles in eine Klasse verpackt. Diese Klasse hat eine abstrakte Property PanelClientSize und ein abstrakte Methode OnPaintPanel().

Die Property _PanelClientSize _wird von einer abgeleiteten Klasse überschrieben und liefert die Größe des geforderten Platzes im Panel. Diese Größe dient zur Dimensionierung der Scrollbars.

Die Methode OnPaintPanel() wird ebenfalls von einer abgeleiteten Klasse überschrieben und sorgt dafür, dass das Panelsteuerelement gepaintet wird.

Ich hoffe, ich kann dem einen oder anderen mit der Klasse das Leben etwas erleichtern.


using System;
using System.Drawing;
using System.Windows.Forms;

namespace BasicControls
{
  public abstract class ScrollablePanel : System.Windows.Forms.UserControl
  {
    protected System.Windows.Forms.HScrollBar m_hScrollBar;
    protected System.Windows.Forms.VScrollBar m_vScrollBar;
    private   System.Windows.Forms.Panel m_ClientPanel;
    private   System.ComponentModel.Container components = null;

    public ScrollablePanel()
    {
      InitializeComponent();
    }

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

    #region Vom Komponenten-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.m_hScrollBar = new System.Windows.Forms.HScrollBar();
      this.m_vScrollBar = new System.Windows.Forms.VScrollBar();
      this.m_ClientPanel = new System.Windows.Forms.Panel();
      this.SuspendLayout();
      // 
      // m_hScrollBar
      // 
      this.m_hScrollBar.Anchor = System.Windows.Forms.AnchorStyles.None;
      this.m_hScrollBar.Location = new System.Drawing.Point(0, 216);
      this.m_hScrollBar.Name = "m_hScrollBar";
      this.m_hScrollBar.Size = new System.Drawing.Size(232, 17);
      this.m_hScrollBar.TabIndex = 0;
      this.m_hScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.OnScrollbarScroll);
      // 
      // m_vScrollBar
      // 
      this.m_vScrollBar.Anchor = System.Windows.Forms.AnchorStyles.None;
      this.m_vScrollBar.Location = new System.Drawing.Point(232, 0);
      this.m_vScrollBar.Name = "m_vScrollBar";
      this.m_vScrollBar.Size = new System.Drawing.Size(17, 216);
      this.m_vScrollBar.TabIndex = 1;
      this.m_vScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.OnScrollbarScroll);
      // 
      // m_ClientPanel
      // 
      this.m_ClientPanel.Anchor = System.Windows.Forms.AnchorStyles.None;
      this.m_ClientPanel.Location = new System.Drawing.Point(0, 0);
      this.m_ClientPanel.Name = "m_ClientPanel";
      this.m_ClientPanel.Size = new System.Drawing.Size(232, 216);
      this.m_ClientPanel.TabIndex = 0;
      // 
      // ScrollablePanel
      // 
      this.Controls.Add(this.m_ClientPanel);
      this.Controls.Add(this.m_vScrollBar);
      this.Controls.Add(this.m_hScrollBar);
      this.Name = "ScrollablePanel";
      this.Size = new System.Drawing.Size(248, 232);
      this.Resize += new System.EventHandler(this.OnResize);
      this.ResumeLayout(false);

    }
    #endregion


    protected abstract Size PanelClientSize { get; }
    protected abstract void OnPaintPanel(object sender, System.Windows.Forms.PaintEventArgs e);

    protected Panel Panel
    {
      get {
        return m_ClientPanel;
      }
      set {
        // remove old panel
        if (m_ClientPanel != null) {
          Controls.Remove(m_ClientPanel);
          m_ClientPanel.Dispose();
        }
        // add new panel
        m_ClientPanel = value;
        Controls.Add(m_ClientPanel);
        m_ClientPanel.Paint += new PaintEventHandler(OnPaintPanel);
        m_ClientPanel.Resize += new EventHandler(OnResize);
        RefreshControls();
        m_ClientPanel.Show();
        m_ClientPanel.Invalidate();
      }
    }

    private bool WillGetHorizontalScrollbar()
    {
      if (PanelClientSize.Width > ClientSize.Width)
        return true;
      
      if (PanelClientSize.Height > ClientSize.Height) {
        // we will get a vertical scrollbar due to needed dimensions, consider it!
        if (PanelClientSize.Width + m_vScrollBar.Width > ClientSize.Width)
          return true;
      }

      return false;
    }

    private bool WillGetVerticalScrollbar()
    {
      if (PanelClientSize.Height > ClientSize.Height)
        return true;

      if (PanelClientSize.Width > ClientSize.Width) {
        // we will get a horizontal scrollbar due to needed dimensions, consider it!
        if (PanelClientSize.Height + m_hScrollBar.Height > ClientSize.Height)
          return true;
      }

      return false;
    }

    protected void RefreshControls()
    {
      // dock horizontal scrollbar
      Point pt;
      pt = new Point(0, ClientSize.Height - m_hScrollBar.Height);      
      m_hScrollBar.Location = pt;
      if (WillGetVerticalScrollbar()) {
        m_hScrollBar.Width = ClientSize.Width - m_vScrollBar.Width;
      } else {
        m_hScrollBar.Width = ClientSize.Width;
      }

      // dock vertical scrollbar
      pt = new Point(ClientSize.Width - m_vScrollBar.Width, 0);
      m_vScrollBar.Location = pt;
      if (WillGetHorizontalScrollbar()) {
        m_vScrollBar.Height = ClientSize.Height - m_hScrollBar.Height;
      } else {
        m_vScrollBar.Height = ClientSize.Height;
      }

      // horizontal scrollbar
      if (WillGetHorizontalScrollbar()) 
      {
        m_hScrollBar.Minimum = 0;
        m_hScrollBar.Maximum = PanelClientSize.Width + 1; // Fehler beseitigt
        m_hScrollBar.SmallChange = 10;
        m_hScrollBar.LargeChange = m_ClientPanel.ClientSize.Width;
        m_hScrollBar.Show();
      } 
      else 
      {
        m_hScrollBar.Minimum = 0;
        m_hScrollBar.Maximum = 0;
        m_hScrollBar.Hide();
      }

      if (WillGetVerticalScrollbar()) 
      {
        m_vScrollBar.Minimum = 0;
        m_vScrollBar.Maximum = PanelClientSize.Height + 1; // Fehler beseitigt
        m_vScrollBar.SmallChange = 10;
        m_vScrollBar.LargeChange = m_ClientPanel.ClientSize.Height;
        m_vScrollBar.Show(); 
      } 
      else 
      {
        m_vScrollBar.Maximum = 0;
        m_vScrollBar.Minimum = 0;
        m_vScrollBar.Hide();
      }

      // recalculate panel size
      Size size = new Size(0,0);
      if (m_hScrollBar.Visible) {
        size.Height = m_hScrollBar.Top;
      } else {
        size.Height = ClientSize.Height;
      }

      if (m_vScrollBar.Visible) {
        size.Width = m_vScrollBar.Left;
      } else {
        size.Width = ClientSize.Width;
      }

      // set new panel size
      m_ClientPanel.Size = size;

      // adjust horizontal scrollbar value to new range
      int nMax = m_hScrollBar.Maximum - m_hScrollBar.LargeChange - 1;
      if (nMax > 0) {
        if (m_hScrollBar.Value > nMax) {
          m_hScrollBar.Value = nMax;
        } 
      } else {
        m_hScrollBar.Value = 0;
      }
      
      // adjust vertical scrollbar value to new range
      nMax = m_vScrollBar.Maximum - m_vScrollBar.LargeChange - 1;
      if (nMax > 0) {
        if (m_vScrollBar.Value > nMax) {
          m_vScrollBar.Value = nMax;
        } 
      } else {
        m_vScrollBar.Value = 0;
      }
    }

    private void OnScrollbarScroll(object sender, ScrollEventArgs e)
    {
      m_ClientPanel.Invalidate();
    }

    private void OnResize(object sender, EventArgs e)
    {
      RefreshControls();
    }
  }
}

Und so kann eine Klasse aussehen, die von ScrollableControl abgeleitet ist:


using System;
using System.Drawing;
using System.Windows.Forms;

public class MyControl : BasicControls.ScrollablePanel
{
  private System.ComponentModel.Container components = null;

  public MyControl()
  {
  }

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

  protected override Size PanelClientSize
  {
    get {
      return new Size(100,100);
    }
  }

  protected override void OnPaintPanel(object sender, System.Windows.Forms.PaintEventArgs e)
  {
    Graphics g = e.Graphics;
    // Hier was tolles painten
  }
}

Ich freue mich auch über konstruktive Kommentare, die mir helfen können, den Code zu verbessern 🙂

20.04.2006 - 11:30 Uhr

Alles klar, das war der Anstoß, den ich brauchte!

Hervorragend, danke!

20.04.2006 - 11:19 Uhr

Hallo Leute,

ich habe mal wieder eine Frage an euch ^^

Ich möchte eine Bitmap auf den Bildschirm pinseln. Die Funktion Graphics.DrawImage interpoliert allerdings das Bild immer, wenn ich einen kleineren Ausschnitt der Bitmap auf einen größeren Bildschirmabschnitt abbilde. Und genau das ist unerwünscht. Ein Pixel sollte bei Zoom-Faktor 3 einfach nur 3x so groß sein.

Ich hoffe, das Problem ist verständlich beschrieben ^^

Ein Dankeschön schon einmal im voraus 🙂

20.04.2006 - 11:01 Uhr

Klare Antwort: noe 😉

Ich habe aber auch noch keine Möglichkeit gefunden, unter C# und mit .NET ein MDI-Fenster zu erzeugen.