Laden...

Skin-Funktionalität für Controls per Skript zur Laufzeit zur Verfügung stellen

Erstellt von Rahvin vor 12 Jahren Letzter Beitrag vor 12 Jahren 1.152 Views
R
Rahvin Themenstarter:in
156 Beiträge seit 2006
vor 12 Jahren
Skin-Funktionalität für Controls per Skript zur Laufzeit zur Verfügung stellen

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?

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo Rahvin,

warum zur Laufzeit? Bist du dir sicher, dass das erforderlich ist? Was willst du eigentlich erreichen?

herbivore

R
Rahvin Themenstarter:in
156 Beiträge seit 2006
vor 12 Jahren

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 😃.

1.665 Beiträge seit 2006
vor 12 Jahren

Ich glaube, dafür kannst du ButtonBase gut verwenden. Also erstelle dir eine Klasse, die davon ableitet.

R
Rahvin Themenstarter:in
156 Beiträge seit 2006
vor 12 Jahren

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.

1.665 Beiträge seit 2006
vor 12 Jahren

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.

R
Rahvin Themenstarter:in
156 Beiträge seit 2006
vor 12 Jahren

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.

1.665 Beiträge seit 2006
vor 12 Jahren

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);
    }
}
R
Rahvin Themenstarter:in
156 Beiträge seit 2006
vor 12 Jahren

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.

1.820 Beiträge seit 2005
vor 12 Jahren

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 🙁

1.665 Beiträge seit 2006
vor 12 Jahren

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);
    });
}
R
Rahvin Themenstarter:in
156 Beiträge seit 2006
vor 12 Jahren

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?

1.665 Beiträge seit 2006
vor 12 Jahren

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.