Membuat Tampilan Button Seperti Office 2010 dengan C#

Posted in: Pemrograman |

This post is also available in: English

button-paint-c-sharp

This post is also available in: English

Di sini saya akan memberikan contoh untuk Owner Drawing/Painting pada suatu component/toolkit di aplikasi desktop (di web kita dapat dengan mudah menggunakan CSS). Kita dapat mendefinisikan tampilan yang berbeda layaknya kita mengaplikasikan theme pada component kita melalui beberapa cara. Misal Button yang awalnya biasa-biasa saja, bisa kita buat berwarna gradasi (gradient) dan tampil jauh lebih menarik.

Pada bahasa pemrograman berbeda mungkin akan ada perbedaan cara. Saya dulu sering coba-coba menggunakan Delphi, dan sekarang ini menggunakan WinForms C#, keduanya hampir sama terutama bila sudah kenal dengan Windows API (Java pun juga menggunakan logika yang sama). Salah satu cara  melalui event painting (biasanya namanya event OnPaint) atau melalui class turunan Renderer, dengan ini kita bisa membuat suatu component/toolkit yang mempunyai tampilan seperti yang kita mau.

Dasarnya adalah metafora yang sama seperti di dunia nyata, kita nantinya akan melakukan proses Painting di Canvas. Setiap saat suatu component akan selalu dilukiskan di canvas supaya tampak secara visual di layar, singkat contohnya pada Button proses painting diawali dari pelukisan backgroundnya kemudian dilukiskan garis tepi (border) hingga muncul teks (bisa saja proses pelukisan akan berbeda urutannya). Hal ini dilakukan terus menerus supaya bisa tampak hingga akhirnya component tersebut di-destroy.

Contoh painting pada Button adalah sebagai berikut:

  1. Buat button1 di Form1.
  2. Tambahkan handler event OnPaint pada Button (pilih Properties, di Events klik dua kali Paint) atau tambahkan di constructorForm, misal:
    public Form1()
    {
    	InitializeComponent();
    	button1.Paint += new System.Windows.Forms.PaintEventHandler(this.button1_Paint);
    }
  3. Kemudian buat procedure button1_paint() berikut:
    private void button1_Paint(object sender, PaintEventArgs e)
    {
        //variables
        Color borderDark, borderLight, bgdark, bgmed, bglight,textColor;
        int _roundedRadiusX = 3;
        int _roundedRadiusY = 3;
    
        //initialization
        //blue button
        borderDark = ColorFromHex("#1f48a1");
        borderLight = ColorFromHex("#4487e4");
        bgdark = ColorFromHex("#2961b5");
        //bgmed = ColorFromHex("#3d7bd9");
        bglight = ColorFromHex("#3e7ddb");
        textColor = Color.White;
    
        //////yellow button
        //borderDark = ColorFromHex("#ecc757");
        //borderLight = ColorFromHex("#fcf3d7");
        //bgdark = ColorFromHex("#f9e189");
        ////bgmed = ColorFromHex("#3d7bd9");
        //bglight = ColorFromHex("#fbf9e0");
        //textColor = ColorFromHex("#1e395b");
    
        //now let's we begin painting
        Graphics g = e.Graphics;
    
        //now let's we begin painting
        //Graphics g = e.Graphics;
        g.SmoothingMode = SmoothingMode.HighQuality;
    
        Rectangle r = new Rectangle(e.ClipRectangle.Left,e.ClipRectangle.Top,e.ClipRectangle.Width,e.ClipRectangle.Height);
        Rectangle r2 = r;
        r2.Inflate(-1, -1);
        Rectangle r3 = r2;
        r3.Inflate(-1, -1);
    
        //clear background first
        using (SolidBrush br = new SolidBrush(Color.FromName("ButtonFace")))
        {
            g.FillRectangle(br, r);
        }
    
        //rectangle for gradient, half upper and lower
        RectangleF halfup = new RectangleF(r.Left, r.Top, r.Width, r.Height);
        RectangleF halfdown = new RectangleF(r.Left, r.Top + (r.Height / 2) - 1, r.Width, r.Height);
    
        //BEGIN PAINT BACKGROUND
        //for half upper, we paint using linear gradient
        using (GraphicsPath thePath = GetRoundedRect(r, _roundedRadiusX, _roundedRadiusY))
        {
            LinearGradientBrush lgb =
                new LinearGradientBrush(halfup, bglight, bgdark, 90f, true);
    
            Blend blend = new Blend(4);
            blend.Positions = new float[] { 0, 0.18f, 0.35f, 1f };
            blend.Factors = new float[] { 0f, .4f, .9f, 1f };
            lgb.Blend = blend;
            g.FillPath(lgb, thePath);
            lgb.Dispose();
    
            //for half lower, we paint using radial gradient
            using (GraphicsPath p = new GraphicsPath())
            {
                p.AddEllipse(halfdown); //make it radial
                using (PathGradientBrush gradient = new PathGradientBrush(p))
                {
                    gradient.WrapMode = WrapMode.Clamp;
                    gradient.CenterPoint = new PointF(Convert.ToSingle(halfdown.Left + halfdown.Width / 2), Convert.ToSingle(halfdown.Bottom));
                    gradient.CenterColor = bglight;
                    gradient.SurroundColors = new Color[] { bgdark };
    
                    blend = new Blend(4);
                    blend.Positions = new float[] { 0, 0.15f, 0.4f, 1f };
                    blend.Factors = new float[] { 0f, .3f, 1f, 1f };
                    gradient.Blend = blend;
    
                    g.FillPath(gradient, thePath);
                }
            }
            //END PAINT BACKGROUND
    
            //BEGIN PAINT BORDERS
            using (GraphicsPath gborderDark = thePath)
            {
                using (Pen p = new Pen(borderDark, 1))
                {
                    g.DrawPath(p, gborderDark);
                }
            }
            using (GraphicsPath gborderLight = GetRoundedRect(r2, _roundedRadiusX, _roundedRadiusY))
            {
                using (Pen p = new Pen(borderLight, 1))
                {
                    g.DrawPath(p, gborderLight);
                }
            }
            using (GraphicsPath gborderMed = GetRoundedRect(r3, _roundedRadiusX, _roundedRadiusY))
            {
                SolidBrush bordermed = new SolidBrush(Color.FromArgb(50, borderLight));
                using (Pen p = new Pen(bordermed, 1))
                {
                    g.DrawPath(p, gborderMed);
                }
            }
            //END PAINT BORDERS
    
            //BEGIN PAINT TEXT
            StringFormat sf = new StringFormat();
            sf.Alignment = StringAlignment.Center;
            sf.LineAlignment = StringAlignment.Center;
    
            if (this.ShowKeyboardCues)
                sf.HotkeyPrefix = HotkeyPrefix.Show;
            else
                sf.HotkeyPrefix = HotkeyPrefix.Hide;
            g.DrawString((sender as Control).Text, (sender as Control).Font, new SolidBrush(textColor), e.ClipRectangle, sf);
    
        }
    
    }
  4. Tambahkan pula fungsi-fungsi berikut ini:
    Untuk membuat rounded rectangle dengan radius yang bisa diset:

    public static GraphicsPath GetRoundedRect(RectangleF r, float radiusX, float radiusY)
    {
        GraphicsPath gp = new GraphicsPath();
        gp.StartFigure();
        r = new RectangleF(r.Left, r.Top, r.Width, r.Height);
        if (radiusX <= 0.0F || radiusY <= 0.0F)
        {
            gp.AddRectangle(r);
        }
        else
        {
            //arcs work with diameters (radius * 2)
            PointF d = new PointF(Math.Min(radiusX * 2, r.Width)
                      , Math.Min(radiusY * 2, r.Height));
            gp.AddArc(r.X, r.Y, d.X, d.Y, 180, 90);
            gp.AddArc(r.Right - d.X, r.Y, d.X - 1, d.Y, 270, 90);
            gp.AddArc(r.Right - d.X, (r.Bottom) - d.Y - 1, d.X - 1, d.Y, 0, 90);
            gp.AddArc(r.X, (r.Bottom) - d.Y - 1, d.X - 1, d.Y, 90, 90);
        }
        gp.CloseFigure();
        return gp;
    }

    Untuk mengubah warna dari format hexadecimal ke Color:

    public static Color ColorFromHex(string hex)
    {
        if (hex.StartsWith("#"))
            hex = hex.Substring(1);
    
        if (hex.Length != 6) throw new Exception("Color not valid");
    
        return Color.FromArgb(
            int.Parse(hex.Substring(0, 2), System.Globalization.NumberStyles.HexNumber),
            int.Parse(hex.Substring(2, 2), System.Globalization.NumberStyles.HexNumber),
            int.Parse(hex.Substring(4, 2), System.Globalization.NumberStyles.HexNumber));
    }
  5. Hasilnya adalah sebagai berikut

    Contoh painting pada Button dengan theme Office 2010

Sebagai catatan source code di atas tidak menggambarkan ketika ada perubahan state dari control, jadi ketika sedang di-sorot dengan mouse (mouse hover/enter) atau di klik (mouse down), dll maka gambarnya akan tetap saja. Supaya dapat menggambarkan perubahan setiap state, buat variable yang mencatat perubahan tersebut saat ada event OnMouseEnter, OnMouseLeave, OnMouseDown, ada focus, dll dan saat painting OnPaint cek state-nya dan lakukan painting yang berbeda tergantung dari state.

Dengan modal ini kita setidaknya dapat membuat tampilan pada custom control yang dapat kita buat sendiri.

Selamat berkreasi!




Speak Up!

Leave your own comment

Notify me of follow-up comments via e-mail (or subscribe here).




 

Share

Subscribe Feed

Email

Facebook

Twitter

Delicious

Digg

StumbleUpon

Google Buzz

Deviantart