Moin moin 😃.
Ich würde gerne zur Laufzeit die OnPaint-Methode der Klasse Button mit User-Code überschreiben.
Geht das irgendwie? Kann ich Alternativ den Button zur Laufzeit von einer anderen Klasse ableiten?
Hallo Rahvin,
warum zur Laufzeit? Bist du dir sicher, dass das erforderlich ist? Was willst du eigentlich erreichen?
herbivore
Hallo herbivore,
ich möchte eine Skin-Funktionalität per Skript zur Verfügung stellen. Das Skript wird zur Laufzeit kompiliert und auf die entsprechende Form angewendet. Der Code sieht im Skript z.B. so aus:
public void Skin(Form frm)
{
frm.BackColor = Color.Black;
foreach (Control c in frm.Controls)
{
if (c.GetType() == typeof(TextBox))
{
((TextBox)c).BackColor = Color.Orange;
}
...
else if (c.GetType() == typeof(Button))
{
Button b = (Button)c;
b.Paint += new PaintEventHandler(delegate(object sender, PaintEventArgs e)
{
Pen blackPen = new Pen(Color.Black, 3);
Rectangle rect = new Rectangle(0, 0, 200, 200);
e.Graphics.DrawRectangle(blackPen, rect);
});
}
}
frm.Paint += new PaintEventHandler(delegate(object sender, PaintEventArgs e)
{
int gradientHeight = 90;
Form form = (Form)sender;
Rectangle rect = new Rectangle(new Point(0, form.Height - gradientHeight), new Size(form.Width, gradientHeight));
LinearGradientBrush brush = new LinearGradientBrush(rect, Color.LightGray, Color.LightGray, 0.0F);
e.Graphics.FillRectangle(brush, rect);
rect = new Rectangle(new Point(0, form.Height - gradientHeight), new Size(form.Width, 1));
brush = new LinearGradientBrush(rect, Color.Black, Color.DarkOrange, 0.0F);
e.Graphics.FillRectangle(brush, rect);
});
}
Das Problem ist in diesem Fall der Button:
else if (c.GetType() == typeof(Button))
{
Button b = (Button)c;
b.Paint += new PaintEventHandler(delegate(object sender, PaintEventArgs e)
{
Pen blackPen = new Pen(Color.Black, 3);
Rectangle rect = new Rectangle(0, 0, 200, 200);
e.Graphics.DrawRectangle(blackPen, rect);
});
}
Wenn ich nur einen neuen EventHandler für das Paint-Ereignis hinzufüge ist das Ergebnis nicht so doll, da der Button immer noch vom System gezeichnet wird. Ich möchte Ihn aber komplett selber zeichnen, da ich dann alle Freiheiten habe.
Falls es eine bessere Lösung gibt bin ich natürlich auch nicht abgeneigt 😃.
Ich glaube, dafür kannst du ButtonBase gut verwenden. Also erstelle dir eine Klasse, die davon ableitet.
> Codejunky <
Ich glaube, dafür kannst du ButtonBase gut verwenden. Also erstelle dir eine Klasse, die davon ableitet.
Meinst Du jetzt zur Laufzeit ableiten? Falls ja wäre das ja auch prima aber wie mache ich das? Google hat mir dabei nicht geholfen.
Zur Design-Zeit die Basisklasse zu verwenden geht nicht, da ich mich ja dann wieder auf eine Klasse festlege.
Zur Design-Zeit, ja.
Es geht ja gerade darum, dass du per Paint-Event den System.Windows.Forms.Button nicht groß beeinflussen kannst. Und ich meine, dass das mit ButtonBase zu bewerkstelligen sein müsste.
> Codejunky <
Ich hab jetzt mal ein bißchen mit ButtonBase rumgespielt aber entweder krieg ich es nicht hin oder ich verstehe nicht genau was Du meinst.
Ich hab jetzt z.B. mal folgendes ausprobiert:
else if (c.GetType() == typeof(Button))
{
ButtonBase b = (ButtonBase)c;
b.Paint += new PaintEventHandler(delegate(object sender, PaintEventArgs e)
{
Pen blackPen = new Pen(Color.Black, 3);
Rectangle rect = new Rectangle(0, 0, 200, 200);
e.Graphics.DrawRectangle(blackPen, rect);
});
}
Es gibt aber keinen Unterscheid wenn ich nur Button verwende.
Na du bist mir ein Held. Es ist ja immer noch ein Windows Forms Button!
Ihn in ButtonBase zu casten ändert doch gar nichts.
public class MyButton : ButtonBase
{
}
Edit: Beim Erstellen des Buttons schreibst du anstatt new Button() --> new MyButton()
und später dann:
else if (c.GetType() == typeof(MyButton))
{
MyButton b = (MyButton)c;
b.Paint += new PaintEventHandler(delegate(object sender, PaintEventArgs e)
{
Pen blackPen = new Pen(Color.Black, 3);
Rectangle rect = new Rectangle(0, 0, 200, 200);
e.Graphics.DrawRectangle(blackPen, rect);
});
}
Wobei ich bei der Gelegenheit das dann eher so machen würde:
public class MyButton : ButtonBase
{
public Skin Skin { get; set; }
protected override void OnPaint(PaintEventArgs e)
{
this.Skin.Paint(this, e); // where Skin : ButtonSkin
base.OnPaint(e);
}
}
public abstract class Skin
{
public void Paint(Control c, PaintEventArgs e)
{
this.Control = c;
this.Graphics = e.Graphics;
// BeforePaint();
this.OnPaint();
// AfterPaint();
}
protected abstract void OnPaint();
}
public class ButtonSkin : Skin
{
// Abstract
protected override void OnPaint()
{
Pen blackPen = new Pen(Color.Black, 3);
Rectangle rect = new Rectangle(0, 0, 200, 200);
this.Graphics.DrawRectangle(blackPen, rect);
}
}
> Codejunky <
Das war ja meine Frage vorhin ob Du das zur Lauf- oder Designzeit meinst.
Meine Buttons alle in ButtonBase (zur Designzeit) umzustellen möchte ich ja auf jeden Fall vermeiden. Das Programm hat zur Zeit an die 100 Buttons, die möchte ich nicht alle auf ButtonBase umstellen. Hinzu kommt noch, dass das nachträgliche Skinnen der Form optional ist. Die Buttons sollten, wenn kein Skin ausgewählt ist auch als Button gezeichnet werden.
Der Button ist hier ja außerdem nur der aktuelle Fall. Es sollen aber auch alle anderen Steuerelemente geändert werden können. Da müsste ich eventuell auch das OnPaint überschreiben oder zur Laufzeit ableiten.
Ich hab sowas ähnliches mal mit C++, Hooks und Subclassing für fremde Programme gemacht. Das ist aber für meine Zwecke jetzt ein bißchen von hinten durch die Brust und zu unflexible und aufwändig.
Hallo!
Du könntest höchtens noch versuchen, bei den vorhandenen Buttons das Paint-Event zu abbonieren und dann den Button im EventHandler zu zeichnen.
Ich weis allerdings nicht, ob man damit die ursprüngliche OnPaint-Methode umgeht.
Und da ich deine gesamte Architektur nicht kenne, kann ich auch nicht sagen, ob dir das hilft.
Nobody is perfect. I'm sad, i'm not nobody 🙁
Du könntest höchtens noch versuchen, bei den vorhandenen Buttons das Paint-Event zu abbonieren
Das tut er doch bisher schon:
else if (c.GetType() == typeof(Button))
{
Button b = (Button)c;
// Hier -->
b.Paint += new PaintEventHandler(delegate(object sender, PaintEventArgs e)
{
Pen blackPen = new Pen(Color.Black, 3);
Rectangle rect = new Rectangle(0, 0, 200, 200);
e.Graphics.DrawRectangle(blackPen, rect);
});
}
> Codejunky <
Ich hab nochmal länger gegooglet.
Kann ich nicht zur Laufzeit die OnPaint-Methode meines Objektes ermitteln und durch eine andere ersetzen? In etwa so:
Button b = new Button();
MethodInfo mf = b.GetType().GetMethod("OnPaint", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
mf ist in diesem Beispiel leider immer NULL aber müsste es nicht so irgendie gehen? Hat vielleicht jemand noch in diese Richtung eine Idee?
Bei Windows-Controls ist das etwas schwerer mit dem Zeichnen.
Du könntest viel eher versuchen, WM_PAINT (Stichwort: P/Invoke) zu behandeln und dort deine Zeichenroutine versuchen.
Aber das ist alles wieder so experimentell, erst recht deine Idee, die Methode technisch zu Überschreiben.
Selbst wenn du sie überschreiben würdest, meines Wissens nach ist das Zeichnen zu dem Zeitpunkt bereits geschehen.
Die einzig wirkungsvolle Variante, einen System.Windows.Forms.Button vom Zeichnen abzuhalten, sehe ich nur in einem eigenen IMessageFilter, der WM_PAINT für Buttons dann einfach nicht behandelt. In etwa so:
if (m.Msg == WM_PAINT)
return true;
Viel Spaß damit.
> Codejunky <