ich glaube er wollte auch Änderungen zum Momentan- und nicht Standardwert haben.
Zudem gibt es Properties die kein DefaultValue haben können (zumindest nicht über das Attribute).
Und da ist die Variante, die ich beschrieben habe, zwar kompliziert, aber wesentlich flexibler.
[AttributeUsage(AttributeTargets.Property)]
public class ValueChangedAttribute : Attribute
{
private bool itsIsChanged = false;
public bool IsChanged
{
get
{
return itsIsChanged;
}
set
{
itsIsChanged = value;
}
}
public ValueChangedAttribute()
{
}
}
Dann eine kleine Testklasse zur Demonstration
public class TestClass
{
private int itsTestValue = 0;
[ValueChangedAttribute()]
public int TestValue
{
get
{
return itsTestValue;
}
set
{
itsTestValue = value;
}
}
public TestClass()
{
}
}
Zuerst mal das Objekt (eine TestClass) dem Grid übergeben, dabei (EINMAL) an den Event (PropertyValueChanged) anmelden.
Dann erscheint die Klasse im Grid.
Solltest du jetzt den Status abfragen, ist dieser "false" (also nix geändert).
Änderst du jetzt das TestValue im Grid (zb von 0 auf 1) und fragst wieder ab,
bekommst du die Information dass der Status "true" ist, das Property wurde also geändert.
der Code im Beispiel ist beim besten Willen nicht zu kompliziert.
Wenn du damit Probleme hast, siehts eh schlecht mit C#-Programmierung aus
Aber der Ablauf ist ganz einfach:
- bei der PictureBox den MouseDownEvent anmelden
- wenn der Event ausgelößt wird, bekommst du in den MouseEventArgs (Parameter der Funktion) die X- / Y-Koordinaten.
- diese einfach merken und nutzen
zu 3.) Wie Programmierhans schon sagte, muss Refresh vom GUI-Thread kommen.
Also entweder BeginInvoke oder Windows.Forms.Timer
RefreshProperties wird dein Problem nicht lösen, da dieses Attribute nur ein Repaint auslößt wenn sich ein Property im GRID ändert (und nicht in der Klasse selbst).
es geht noch speicherschonender, wenn du bei den Pens nur Farbe und Breite änderst und bei den Brushs nur die Farbe änderst. Dabei musst du aber deine Zeichenreihenfolge beachten.
Denn genau diese Properties kann du ändern, ohne das GDI-Objekt neu instanziieren zu müssen (im Gegensatz zum Font.Size, etc).
Du kannst also nur einen globalen Pen und einen globalen Brush anlegen und deine Get-Funktionen entsprechend anpassen.
/// <summary>
/// Übergibt ein Pen-Objekt
/// </summary>
/// <param name="color">Farbe des Pens</param>
/// <returns>der Pen</returns>
public Pen GetPen(Color color)
{
itsGlobalPen.Color = color;
return itsGlobalPen;
}
/// <summary>
/// Übergibt ein Pen-Objekt
/// </summary>
/// <param name="color">Farbe des Pens</param>
/// <param name="size">Größe des Pens</param>
/// <returns>der Pen</returns>
public Pen GetPen(Color color, float size)
{
itsGlobalPen.Color = color;
itsGlobalPen.Width = size;
return itsGlobalPen;
}
und dann zeichnen mit beiden Stiften geht NICHT. (beide wären grün)
Du musst deine Reihefolge beachten und immer den Pen neu holen, falls du mit einer anderen Farbe zeichnen willst.
Wenn ich ein Dialogfenster (Einstellungen, Info, ...) über der Main Form Bewege dann sieht man deutlich das Neuzeichnen, darum ging es mir
probiere doch einfach mal mein Beispiel aus ... es wird nix neugezeichnet, auch wenn ein Dialog drüber geschoben wird. Warum das so ist, habe ich ja schon erwähnt ...
ich habe mit den neune Listen-Klassen noch nicht gearbeitet, deswegen weiß ich nicht ob die generischen Listen Dispose haben und für ihre enthaltenen Objekte auf IDisposable prüfen und ggfs aufrufen ....
Auf Nummer Sicher gehst du aber auf jedenfall, wenn du selbst die Listen-Elemente durchläufst und selbst Dispose für jedes einzelne Element aufrufst.
oh ja eine Frage hab ich noch... Wenn ich GDI Objekte innerhalb von einer Funktion erstelle, werden diese bei beendigung der funktion auch wieder zerstört ? oder soll ich auch da dispose vorher aufrufen ?
Dispose MUSS aufgerufen werden, wenn du Using nicht verwendest... auch innerhalb einer Funktion.
Normalfall ist sehr relativ ... du musst dir das so vorstellen. Wenn du zb new Pen sagst, trägt sich dieser Pen im hintergrund in einer Art Table ein um schnellen zugriff zu sichern und Speicher zu reservieren. Sagst du nun NICHT Dispose bleibt dieser Pen während der Laufzeit ewig in dieser Table ....
Zitat
Bzw. Würde mir die IDisposable Implementierung eigentlich leider nichts bringen, da die GDI Objekte ja alle immer nur innerhalb von Funktionen gültig sind .. Oder seh ich das Falsch ? .. d.h. besser wäre es, wenn ich using verwende immer innerhalb der funktionen
Also ich habe selbst eine komplexe GDI Anwendung geschrieben und nach einigem Testen bin ich auf folgende Lösung was ZeichenObjekte wie Pens und Brushs angeht gekommen:
- Instanziieren von Zeichen Objekten kosten recht viel Zeit, Speicher und Dispose muss beachtet werden: alle diese Probleme lösst eine statische BackroundHasttable.
Diese wird bei Programmstart instaziiert, bei Programmende verworfen. Während der Ausführung brauchen verschiedene Objekte zu verschiedenen Zeiten verschieden Pens / Brushes. Dazu gibt es dann die statische Methode GetBrushByColor(Color theColor). Diese macht nix anderes, als in der hashtable über die Color (==key) zu schauen ob der Eintrag existiert und im erfolgfalls das Objekt (== Brush) zurückzuliefern oder wenn diese nicht existiert, einen neue Eintrag in der Hashtable zu machen, wo ein neuer Brush instaziiert wird (mit der entsprechenden Farbe). Der Vorteil: wenn verschiedene Objekte einen Brush zb mit der Farbe Blau benötigen, wird dieser Brush genau EINMAL instanziiert. danach erfolgt der Zugriff Just-In-Time. Über das Dispose brauchst du dir auch keine Gedanken zu machen, da dies ja erst zur Programmbeendigung nötig ist. Zudem wird ja nur eine definierte Anzahl von Objekten erstellt, welche du ggfs wiederverwendest.
Diese Methode ist äußerst effektiv, wenn du sichergehen kann, dass nur eine bestimmte Zahl von verschiedenfarbigen Brushes / Pens benötig wird. Zeichnest du mit 1000 verschiedenen farben, musst du dir selbst was einfallen lassen
Zitat
Bezüglich Exception .. Ich bin ein sehr "ängstlicher" Mensch und habe zumindest in jeder Funktion, die mit GDI+ Elementen arbeitet ein try-catch eingebaut .. Ist das sinnlos ? Soll ich annehmen dass sowas immer klappt oder doch drinnen lassen ?
theoreitsch sollte es reichen, wenn du in der Hauptzeichenroutine gehst und dort mit try-catch arbeitest.
Hmm ich will dich ja nicht nerven, aber selbst zum Zeichnen einer Table
brauchst du das nicht.
Ich habe mal quick'n'dirty etwas Code geschrieben und ein Bild angehangen.
Sollte das nicht reichen?
// Table erstellen
DataTable aTable = new DataTable();
aTable.Columns.Add(new DataColumn("Spalte 1"));
aTable.Columns.Add(new DataColumn("Spalte 2"));
aTable.Columns.Add(new DataColumn("Spalte 3"));
aTable.Rows.Add(new object[] {"Wert 1", "Wert 2", "Wert 3"});
aTable.Rows.Add(new object[] {"Wert 4", "Wert 5", "Wert 6"});
aTable.Rows.Add(new object[] {"Wert 7", "Wert 8", "Wert 9"});
// Bild
Image aImage = new Bitmap(pictureBox1.Width, pictureBox1.Height);
// Graphics zum Zeichnen
Graphics aGraphics = Graphics.FromImage(aImage);
aGraphics.SmoothingMode = SmoothingMode.HighQuality;
// StringFormat
StringFormat aStringFormat = new StringFormat();
aStringFormat.Alignment = StringAlignment.Center;
aStringFormat.LineAlignment = StringAlignment.Center;
// Font
Font aFont = new Font("Arial", 11.0f);
// BorderPen
Pen aBorderPen = new Pen(Color.Blue, 2.0f);
// FontBrush
SolidBrush aFontBrush = new SolidBrush(Color.Black);
// StartPos
Point aPos = new Point(10, 10);
// Cells
foreach (DataRow aDataRow in aTable.Rows)
{
object[] aRow = aDataRow.ItemArray;
Rectangle aRect = new Rectangle(0, 0, 1, 1);
foreach (object aRowEntry in aRow)
{
Size aSize = aGraphics.MeasureString(aRowEntry.ToString(), aFont, 500, aStringFormat).ToSize();
aRect = new Rectangle(aPos, aSize);
// Celle vergrößern, damit Text etwas mehr Platz hat
aRect.Inflate(5, 5);
aGraphics.DrawString(aRowEntry.ToString(), aFont, aFontBrush, aRect, aStringFormat);
aGraphics.DrawRectangle(aBorderPen, aRect);
aPos.X += aRect.Width + 5; // Zwischenraum X-Richtung
}
aPos.X = 10;
aPos.Y += aRect.Height + 5; // Zwischenraum Y-Richtung
}
// Zuweisung
pictureBox1.Image = aImage;
// Dispose
aGraphics.Dispose();
aStringFormat.Dispose();
aFont.Dispose();
aBorderPen.Dispose();
aFontBrush.Dispose();
ja, jetzt bin ich wohl schlauer . Die Frage ist aber nach wie vor, ob
du diese Werte überhaupt brauchst. Und zum Ausrichten brauchst du sie halt nicht.
- du fügt eine neue Zeile mit "+=" hinzu
- die TextBox verwirft alle Formatierungen, es passiert aber nix, da ja noch kein Text vorhanden ist
- die markierte Zeile (== die neue Zeile) wird schwarz markiert (SelectionColor = Color.Black)
[Bis hierhin alles super]
- du fügst wieder eine neue Zeile hinzu
- wieder verwirft die TextBox alle Formatierungen, dh alle Texte bekommen den Standard-Font und die Standard-Farbe (welche Defaultmäßig schwarz ist).
- die erste Zeile verliert also die Formatierung und wird mit Standard-Einstellungen gezeichnet (== Schwarz).
- die markierte Zeile (== neue Zeile == 2. Zeile) wird grün markiert
[Hier hat bereits der Fehler stattgefunden, du hast es nur nicht gemerkt, da deine Formatierung der ersten Zeile gleich der Standard-Formatierung ist]
- jetzt kommt Zeile 3 und es geht genauso fröhlich weiter (also alle Formatierungen werden gelöscht == alle Zeilen im Standard-Format) und die neue Zeile bekommt die gesetzte Farbe.
zufügst, dann registiert dies die TextBox als würde der komplett neuer Text gesetzt werden. (+= ruft zuerst GETTER dann SETTER des Properties ab, und im SETTER wird der neue Text, welcher aus alter Text + aLine besteht, gesetzt).
Dadurch verfallen sämtliche Formatierungen!
Dadurch entweder Methode 1: den ganzen Text checken und neu formatieren (diese Methode ist Pflicht, falls komplexe Änderungen stattfinden)
oder Methode 2: du fügst den String per "AppendText"-Funktion hinzu. Mit dieser Funktion registriert die TextBox lediglich einen hinzugefügten Teil-String, welcher die aktuelle Formatierung bekommt (hast du also vorher SelectionColor auf Blue gesetzt, bekommt auch dieser neue String eine blaue Farbe). Diese Methode ist natürlich wesentlich schneller, aber unflexibler.
Logik: Der kompletter Text wird aufgrund eines Trennzeichens ("\r\n" == NEWLINE) gesplittet. Die einzelnen Zeilen werden durchlaufen und in jeder Zeile eine bestimmte farbe gesetzt (hier: einfach jede 2. Grün).
also wenn ich dich richtig verstanden habe, willst du eigentlich nur von deinem Hauptfenster aus ein "Unterfenster" öffnen, sodass dies sichtbar ist, aber dein Hauptfenster soll weiterhin aktiv bleiben ....
dann versuch mal nur "Show" statts "ShowDialog".
Wenn du dann willst, dass das "Unterfenster" noch sichtbar ist, auch wenn die Hauptform aktiv ist (wie zb bei der Suchfunktion in Visual Studio) dann musst du im "Unterfenster "das Property "TopMost" auf "true" setzen.
Instanzen von untypisierten Objekten zu erstellen ist eine ehr schlechte Idee .
es gibt mehrere Möglichkeiten und "CreateInstance" ist eine davon. Da du der keine Parameter übergibst, können auch nur UserControls erstellt werden, die parameterfreie Kontruktoren enthalten .... nicht sehr flexibel.
Eine flexiblere Variante sind Interfaces. Diese beinhalten zb eine Funktion namens "CreateDefaultInstance" (oder was auch immer ) und liefert ein "UserControl" zurück.
Jedes deiner komplexen UserControls (welche dann auch von Windows.Forms.UserControl abgeleitet sein müssen!), implementiert dieses Interface, und liefert in der Funktion eben eine neue Instanz von sich selbst zurück. In deiner Funktion "OpenUserControl" checkst du, ob dieser Interface implementiert ist (is-Operator) castest und rufst die Funktion "CreateDefaultInstance"auf.
Aber ganz im Ernst: du solltest dich fragen, ob dieser Aufwand wirklich nötig ist.
Warum willst du denn die Controls erst "OnDemand" instanziieren?
die einfachste möglichkeit ist, einfach NICHT jedes mal neuzuzeichnen. Und die erreichst du beispielsweise mit Bitmaps.
Momentan hängst du dich in den OnPaintEvent rein oder?
Verwerfe das, und versuche folgende Schritte:
- einmalig (zb im Load-Event) ein Background-Bitmap mit deinem LinearGradientBrush erstellen
- dazu new Bitmap (in der Größe des Panels), dann über Graphics.FormImage() das Graphics-Objekt erstellen, dann über das Graphics-Objekt den Farbverlauf in das Bitmaps zeichnen, dann Graphics disposen und das Bitmap dem Panel als backgroundimage übergeben
jetzt wird der farbverlauf nicht mehr andauernd neugezeichnet. Der Nachteil ist halt, dass dieser auch nicht mehr neugezeichnet wird, wenn sich die Form (oder Panel) in der Größe ändert. Ist dies nötig, musst du dich in den SizeChangedEvent reinhängen und die selbe Funktion aufrufen.
Der WrapMode gibt ja den Umbruchbereich an für zu große (und auch für zu kleine Bitmaps).
Mit Clamp sagst du halt, dass ein möglicher Farbverlauf nicht über die Objekt-Grenze hinweg berechnet wird.
warum so umständlich? Es gibt doch für das Zeichnen von Graphen schon die sehr gute Bibliothek von ZedGraph.
Diese ist mächtig genug, damit du deine Probleme damit lösen kannst.
bmp zu jpeg geht recht einfach, wenn du mit Image.FromFile das bmp lädst und danach
mit Image.Save als jpeg wieder speicherst (einfach Endung setzen oder ImageFormat).
Dies geht im normalen Framework super. Sollte dies vom "OPENnetCF Smart Device Framework" nicht unterstützt sein, musst du wohl einen komplett anderen Weg gehen.
Resize eines Bilder ist ähnlich einfach.
- per Image.FromFile das Original laden
- mit new Bitmap(new Width, new Height) das neue (kleinere) Bild anlegen
- Graphics auf das neue Bild legen (Graphics.FromImage)
- mit Graphics das große Bild ins kleine Bild zeichnen (aGraphics.DrawImage)
- Graphics schließen / verwerfen
- das kleine Bild speichern (Image.Save)