Monday, October 14, 2013

Custom Shape Control VB. NET

Indonesian Version Available Below (After The English Version)

This time we will try to create a "Custom Shape Area" control. This example is only a small part of the original application, which is a Shopping Mall floor-plan. In the original application, the user can view a map of a floor and click at a shop (the shops are shaped like the original, which is why a custom shape control is required), to view info about the shop.

I found the idea of this "Custom Shape Area" control on an Article in CodeProject that explains about making a VB6 circle shaped control  (in VB. NET, this circle control is no longer provided in the toolbox).

We will start this example by simple shapes such as Ellipse and Rectangle, and after that we will talk about the custom shape control (using polygons).



1. Create a New Project, name it CustomShapeProject, project type is Windows Application.

2. Right click on the Solution Explorer, choose "Add New Item", and then select "Custom Control". Name it CEllipse.

3. In Code View, add Import(s) for namespace System.Drawing and System.Drawing2D.

 Imports System.Drawing  
 Imports System.Drawing.Drawing2D  

4. Insert the code below under the writing "add your custom paint code here"

     Dim path As GraphicsPath = New GraphicsPath()  
     path.AddEllipse(0, 0, Me.ClientSize.Width, Me.ClientSize.Height)  
     Me.Region = New Region(path)  
     Dim rc As Rectangle = New Rectangle(0, 0, Me.ClientSize.Width, Me.ClientSize.Height)  
     Dim _InsideBrush As Brush = Brushes.Yellow  
     Dim _OutlinePen As Pen = New Pen(Color.Black, 2)  
     e.Graphics.FillEllipse(_InsideBrush, rc)  
     e.Graphics.DrawEllipse(_OutlinePen, rc)  


5. Right click on the project name at Solution Explorer, choose build, and wait for Visual Studio compiling.

6. Go to the design view of Form1, make sure that the Visual Studio Toolbox is visible. Now you can see there's a section labeled "CustomShapeProject Components", and CEllipse in that section.



7. Drag CEllipse from toolbox to Form1, an Ellipse would appear on Form1. Try to run the project.

Explanation :
  • We create ellipse by using a GraphicsPath class. You could see that besides path.AddEllipse, there are also path.AddRectangle and path.AddPolygon which we will use later.
  • We set the drawable region from class CEllipse by using the path created. 
  • To make the ellipse we use e.Graphics.FillEllipse for the inside part of the Ellipse, and e.Graphics.DrawEllipse for the outline. Both FillEllipse and DrawEllipse is drawn inside a rectangle, using a brush and/or a pen, so we must create them first.
  • The Rectangle is given the same size as the Ellipse
  • We make the brush Yellow
  • And the pen is Black with 2 pixels width.
  • To make CEllipse appear in the Visual Studio Toolbox, we build the project.


Now let's add some features to CEllipse, such as text in the middle of the Ellipse (Text property), and change color on mouse hover.

1. Go to Code View on CEllipse, add this piece of code in Sub OnPaint after the previous code.

  Dim _BrushText As Brush = Brushes.Black  
  Dim StringSize As SizeF = e.Graphics.MeasureString(Me.Text, Me.Font)  
  Dim StartPoint_X As Single = (Me.ClientSize.Width / 2) - (StringSize.Width / 2)  
  Dim StartPoint_Y As Single = Me.ClientSize.Height / 2 - (StringSize.Height / 2)  
  e.Graphics.DrawString(Me.Text, Me.Font, _BrushText, New PointF(StartPoint_X, StartPoint_Y))  



In the above piece of code, we measure the width of the string we will place on the Ellipse, and then we calculate the coordinates inside the Ellipse, and after that we just draw the string using a black brush.

Try to run the project again, there's should be a text "CEllipse1" inside the ellipse, which is a default value of the text property. Of course we could change it in the Properties Window.



2. And now for the mouse hover.

Move variable declaration for _InsideBrush from sub OnPaint to outside of the SubProcedure, such as under the Class Declaration.
 Dim _InsideBrush As Brush = Brushes.Yellow  



 3. Let's make the event handler. In the top left of the code view, select CEllipse Events, and on the top right choose MouseEnter and MouseLeave. Visual Studio will create the event handler SubProcedures. Put these codes inside them.

  Private Sub CEllipse_MouseEnter(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.MouseEnter  
     _InsideBrush = Brushes.Blue  
     Me.Invalidate()  
   End Sub  
   Private Sub CEllipse_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.MouseLeave  
     _InsideBrush = Brushes.Yellow  
     Me.Invalidate()  
   End Sub  



4. Try to run the program to see the intended effects. Notice that everytime the mouse cursor enters and leaves the Ellipse, it changes color. We could also do the same for the color of the text.



I will not give any example for the rectangle shape, it's very easy. All we have to do is to change :

path.AddEllipse to path.AddRectangle
e.Graphics.FillEllipse to e.Graphics.FillRectangle
e.Graphics.DrawEllipse to e.Graphics.DrawRectangle

just don't forget to adjust  the parameters ;)

And now let's move on create a Custom Shape.

To create a custom shape, we're going to use polygons. to Create a Polygon, we need to specifies for the coordinates (for the 'joints'). As an example we're going to do a triangle first (3 points).

1. Right click at the project name in the Solution Explorer, Choose Add New Item, and select "Custom Control". Name it CCustomShape.

2. Just like the CEllipse example, add Imports for namespaces System.Drawing and System.Drawing2D


 Imports System.Drawing  
 Imports System.Drawing.Drawing2D  


3. Put the code below inside sub OnPaint(), under the text  "Add your custom paint code here"

     Dim path As GraphicsPath = New GraphicsPath()  
     Dim _points(2) As Point  
     _points(0) = New Point(0, 0)  
     _points(1) = New Point(20, 0)  
     _points(2) = New Point(0, 20)  
     path.AddPolygon(_points)  
     Me.Region = New Region(path)  
     Dim _InsideBrush As Brush = Brushes.Yellow  
     Dim _OutlinePen As Pen = New Pen(Color.Black, 2)  
     e.Graphics.FillPolygon(_InsideBrush, _points)  
     e.Graphics.DrawPolygon(_OutlinePen, _points)  



4. Rebuild the project, after the CCustomShape appears in the visual studio toolbox, drag it to Form1, you can see it.



In the example above, we created a Triangle using 3 points. Because the paremeters for path.AddPolygon, e.Graphics.FillPolygon and e.Graphics.DrawPolygon requests for an array, so we put those 3 points in an array (of points).

Now, let's modify this example so that the points are not hardcoded inside the class.

5. Remove the newly added CCustomShape object from Form1

6. Undo the third step above, so that OnPaint() returns to it's original state.

7. Add variable _Points under the CCustomShape Class Declaration. It's modifier is Private so let's create the Public Property Points to access it. We're going to use the help of List (Of T) so that it's easier.

   Private _Points As New List(Of PointF)  
   Public Property Points() As List(Of PointF)  
     Get  
       Return _Points  
     End Get  
     Set(ByVal value As List(Of PointF))  
       _Points = value  
     End Set  
   End Property  



Note : PointF instead of Point, because somehow when I used Point, it doesn't look very good in the properties window.

8. Add the piece of code below inside Sub OnPaint(), at the same time we'll add code for Text Property like in the CEllipse example.

     If _Points.Count > 2 Then  
       Dim path As GraphicsPath = New GraphicsPath()  
       path.AddPolygon(_Points.ToArray)  
       Me.Region = New Region(path)  
       Dim _InsideBrush As Brush = Brushes.Yellow  
       Dim _OutlinePen As Pen = New Pen(Color.Black, 2)  
       e.Graphics.FillPolygon(_InsideBrush, _Points.ToArray)  
       e.Graphics.DrawPolygon(_OutlinePen, _Points.ToArray)  
     End If  
     Dim _BrushText As Brush = Brushes.Black  
     Dim StringSize As SizeF = e.Graphics.MeasureString(Me.Text, Me.Font)  
     Dim StartPoint_X As Single = (Me.ClientSize.Width / 2) - (StringSize.Width / 2)  
     Dim StartPoint_Y As Single = Me.ClientSize.Height / 2 - (StringSize.Height / 2)  
     e.Graphics.DrawString(Me.Text, Me.Font, _BrushText, New PointF(StartPoint_X, StartPoint_Y))  



9.  We're going to programatically add a custom shape in Form1 with the shape of a Rhombus(Kite). I already made a Tutorial about "Adding Controls at Runtime", you're welcome to look at it if you need to. Put this code inside the Load Event Handler of Form1.
     Dim Shape1 As New CCustomShape  
     Dim Shape2 As New CCustomShape  
     Shape1.Width = 100  
     Shape1.Height = 150  
     Shape2.Width = 100  
     Shape2.Height = 150  
     Shape1.Points.Add(New Point(50, 0))  
     Shape1.Points.Add(New Point(100, 50))  
     Shape1.Points.Add(New Point(50, 150))  
     Shape1.Points.Add(New Point(0, 50))  
     Shape2.Points.Add(New Point(50, 0))  
     Shape2.Points.Add(New Point(100, 100))  
     Shape2.Points.Add(New Point(50, 150))  
     Shape2.Points.Add(New Point(0, 100))  
     Shape1.Location = New Point(50, 50)  
     Shape2.Location = New Point(150, 50)  
     Shape1.Text = "Shape1"  
     Shape2.Text = "Shape2"  
     Me.Controls.Add(Shape1)  
     Me.Controls.Add(Shape2)  


10. Try to run the program, you will see that there are 2 Rhombus on the form.



11. You can also try to drag the CCustomShape control fro the Visual Studio Toolbox, and set its coordinates in the Points Property at Property Window, but when I tried that, I've encountered some errors, and after some tinkering, I've decided to just write instructions no 9 instead, to avoid further complexities :D

That's all I suppose.


Indonesian Version

Kali ini kita akan membahas mengenai pembuatan custom shape area control. Contoh yang diberikan ini hanya bagian yang kecil dari program aslinya. Program aslinya adalah untuk pemetaan sebuah Mall (pusat perbelanjaan), dimana user dapat melihat peta sebuah lantai, dan mengklik suatu toko untuk mendapatkan informasi mengenai toko tersebut.

Ide untuk program "Custom Shape Area Control" ini didapat dari sebuah artikel di CodeProject, yang membahas mengenai membuat control lingkaran ala VB6 (di VB.NET, control ini sudah tidak tersedia di toolbox).

Kita akan mulai dari contoh sederhana yaitu Elips (Ellipse) dan Kotak (Rectangle) dulu, baru setelah itu lanjut ke custom shape (dengan menggunakan Polygon).

1. Create Project Baru, beri nama CustomShapeProject, dengan tipe windows application

2. Klik kanan di nama project pada solution explorer, Pilih Add New Item, kemudian pilih Custom Control. Beri Nama CEllipse

3. Tambahkan di paling atas, Import untuk namespace System.Drawing dan System.Drawing2D

 Imports System.Drawing  
 Imports System.Drawing.Drawing2D  

4. Masukkan coding ini di dalam sub OnPaint, dibawah tulisan "Add your custom paint code here"

     Dim path As GraphicsPath = New GraphicsPath()  
     path.AddEllipse(0, 0, Me.ClientSize.Width, Me.ClientSize.Height)  
     Me.Region = New Region(path)  
     Dim rc As Rectangle = New Rectangle(0, 0, Me.ClientSize.Width, Me.ClientSize.Height)  
     Dim _InsideBrush As Brush = Brushes.Yellow  
     Dim _OutlinePen As Pen = New Pen(Color.Black, 2)  
     e.Graphics.FillEllipse(_InsideBrush, rc)  
     e.Graphics.DrawEllipse(_OutlinePen, rc)  




5. Klik kanan di nama project pada solution explorer, Pilih build. Program akan melakukan kompilasi

6. Munculkan Design View dari Form1 dan Toolbox. Sekarang akan muncul bagian CustomShapeProject Components di toolbox, dengan anggotanya CEllipse.


7. Klik and drag CEllipse dari toolbox ke Form1. Akan muncul sebuah elips di Form1. Coba Run Projectnya.

Penjelasan :
  • Kita membuat elips dengan menggunakan Class GraphicsPath. selain path.AddElipse, ada juga path.AddRectangle dan path.AddPolygon yang akan kita gunakan nanti. 
  • Kita set Region dari class CEllipse dengan menggunakan bentuk yang sudah kita buatkan pathnya. 
  • Untuk menggambar elipsenya supaya terlihat, kita akan menggunakan e.Graphics.FillEllipse (untuk isinya), dan e.Graphics.DrawEllipse (kita berikan bingkai supaya lebih terlihat). Baik FillEllipse dan DrawEllipse membutuhkan suatu rectangle, suatu brush dan suatu pen, karena itu kita buat dulu :
  • Rectanglenya kita buat dengan ukuran yang sama dengan class Ellipse
  • Brush-nya kita buat berwarna kuning
  • Pen-nya kita buat berwarna hitam dengan lebar 2 pixel.
  • Supaya class CEllipse muncul di toolbox, maka kita perlu build dulu projectnya. Baru setelah itu komponen CEllipse dapat kita gunakan.

Sekarang kita akan mempermanis class CEllipse ini sedikit, yaitu dengan menambahkan tulisan di tengahnya (Property Text), dan kita buatkan mekanisme supaya kalau mouse lagi hover di atas ellipse tersebut, akan berubah warna.

1. Buka lagi class CEllipse (code view). Tambahkan koding sedemikian di dalam Sub Onpaint setelah coding sebelumnya

  Dim _BrushText As Brush = Brushes.Black  
  Dim StringSize As SizeF = e.Graphics.MeasureString(Me.Text, Me.Font)  
  Dim StartPoint_X As Single = (Me.ClientSize.Width / 2) - (StringSize.Width / 2)  
  Dim StartPoint_Y As Single = Me.ClientSize.Height / 2 - (StringSize.Height / 2)  
  e.Graphics.DrawString(Me.Text, Me.Font, _BrushText, New PointF(StartPoint_X, StartPoint_Y))  


Maksud dari coding di atas adalah kita mengukur panjang string yang akan ditulis, kemudian kita menghitung string tersebut akan mulai dari koordinat berapa di dalam class CEllipse, dan kita gambar string tersebut, dengan menggunakan brush berwarna hitam.

Coba jalankan lagi programnya, sekarang seharusnya akan muncul tulisan CEllipse1 di dalam komponen elips kita (teks CEllipse1 adalah isi dari property text, bisa diganti di properties window)


2. Sekarang untuk mekanisme mouse hover.

Pindahkan deklarasi variabel untuk _InsideBrush dari dalam Sub OnPaint ke luar sub (biasanya kita taruh di bawah deklarasi nama class) sbb:

 Dim _InsideBrush As Brush = Brushes.Yellow  


3. Kita buatkan event handlernya : Pada bagian kiri atas, pilih CEllipse Events, dan di kanannya pilih event MouseEnter dan MouseLeave sehingga oleh visual studio, dibuatkan event handlernya. Isi sedemikian


  Private Sub CEllipse_MouseEnter(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.MouseEnter  
     _InsideBrush = Brushes.Blue  
     Me.Invalidate()  
   End Sub  
   Private Sub CEllipse_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.MouseLeave  
     _InsideBrush = Brushes.Yellow  
     Me.Invalidate()  
   End Sub  



4. Coba Run lagi programnya. Perhatikan bahwa setiap kali kita hover di elips tersebut, warnanya berubah. Kita bisa pula melakukan hal yang sama untuk warna tulisannya.


Untuk bentuk yang kotak (rectangle), sebenarnya mudah sekali, yaitu kita hanya perlu mengganti 
path.AddEllipse, e.Graphics.FillEllipse, e.Graphics.DrawEllipse dengan path.AddRectangle, e.Graphics.FillRectangle, e.Graphics.DrawRectangle. Tentu saja parameternya harus disesuaikan. Kita akan skip pembuatan class Rectangle ini, dan langsung lanjut ke pembuatan Custom Shape.

Custom shape kita buat menggunakan bantuan polygon. Untuk membuat polygon, kita perlu membuat titik-titiknya terlebih dahulu. Untuk contohnya, kita akan gunakan segitiga (3 buah titik).

1. Klik kanan di nama project pada solution explorer, Pilih Add New Item, kemudian pilih Custom Control. Beri Nama CCustomShape
2. Tambahkan di paling atas, Import untuk namespace System.Drawing dan System.Drawing2D



 Imports System.Drawing  
 Imports System.Drawing.Drawing2D  

3. Copy paste coding ini di dalam sub OnPaint, dibawah tulisan 'Add your custom paint code here

     Dim path As GraphicsPath = New GraphicsPath()  
     Dim _points(2) As Point  
     _points(0) = New Point(0, 0)  
     _points(1) = New Point(20, 0)  
     _points(2) = New Point(0, 20)  
     path.AddPolygon(_points)  
     Me.Region = New Region(path)  
     Dim _InsideBrush As Brush = Brushes.Yellow  
     Dim _OutlinePen As Pen = New Pen(Color.Black, 2)  
     e.Graphics.FillPolygon(_InsideBrush, _points)  
     e.Graphics.DrawPolygon(_OutlinePen, _points)  


4 Build ulang projectnya, setelah itu kembali ke design view form1, dan dari toolbox, tarik komponen CCustomShape ke form. Dapat dilihat bahwa terbentuk segitiga di form1.


Contoh tadi adalah membuat segitiga dengan menggunakan tiga titik (point), karena parameter untuk path.AddPolygon, e.Graphics.FillPolygon dan e.Graphics.DrawPolygon mensyaratkan parameter dalam bentuk array, maka kita satukan ketiga titik tersebut dalam bentuk array (of points). 

Kita akan modifikasi contoh ini supaya titik-titiknya bisa kita tentukan dari luar class.

5. Hapus komponen CCustomShape yang sudah ditambahkan di Form1.

6. Kembalikan kondisi sub OnPaint  seperti sebelum ditambahkan baris program di nomor 3

7. Tambahkan variabel _Points di luar sub onPaint, di bawah Public Class CCustomShape, dan kita tambahkan Property Points untuk mengaksesnya. Di sini kita akan menggunakan bantuan List (Of T) supaya lebih mudah.


   Private _Points As New List(Of PointF)  
   Public Property Points() As List(Of PointF)  
     Get  
       Return _Points  
     End Get  
     Set(ByVal value As List(Of PointF))  
       _Points = value  
     End Set  
   End Property  


Catatan : di sini digunakan PointF, bukan Point, karena kalau pakai Point, hasilnya gak bagus di properties window.

8. Tambahkan coding berikut di dalam sub OnPaint, sekaligus kita tambahkan coding untuk tulisannya, sama seperti waktu kita menampilkan tulisan di dalam elips.

     If _Points.Count > 2 Then  
       Dim path As GraphicsPath = New GraphicsPath()  
       path.AddPolygon(_Points.ToArray)  
       Me.Region = New Region(path)  
       Dim _InsideBrush As Brush = Brushes.Yellow  
       Dim _OutlinePen As Pen = New Pen(Color.Black, 2)  
       e.Graphics.FillPolygon(_InsideBrush, _Points.ToArray)  
       e.Graphics.DrawPolygon(_OutlinePen, _Points.ToArray)  
     End If  
     Dim _BrushText As Brush = Brushes.Black  
     Dim StringSize As SizeF = e.Graphics.MeasureString(Me.Text, Me.Font)  
     Dim StartPoint_X As Single = (Me.ClientSize.Width / 2) - (StringSize.Width / 2)  
     Dim StartPoint_Y As Single = Me.ClientSize.Height / 2 - (StringSize.Height / 2)  
     e.Graphics.DrawString(Me.Text, Me.Font, _BrushText, New PointF(StartPoint_X, StartPoint_Y))  


9. Pada event Load dari Form1, kita akan menambahkan sebuah custom shape berbentuk layang-layang. Kita  akan menambahkan secara runtime, yang sudah dibahas di tutorial "Adding Controls at Runtime"


     Dim Shape1 As New CCustomShape  
     Dim Shape2 As New CCustomShape  
     Shape1.Width = 100  
     Shape1.Height = 150  
     Shape2.Width = 100  
     Shape2.Height = 150  
     Shape1.Points.Add(New Point(50, 0))  
     Shape1.Points.Add(New Point(100, 50))  
     Shape1.Points.Add(New Point(50, 150))  
     Shape1.Points.Add(New Point(0, 50))  
     Shape2.Points.Add(New Point(50, 0))  
     Shape2.Points.Add(New Point(100, 100))  
     Shape2.Points.Add(New Point(50, 150))  
     Shape2.Points.Add(New Point(0, 100))  
     Shape1.Location = New Point(50, 50)  
     Shape2.Location = New Point(150, 50)  
     Shape1.Text = "Shape1"  
     Shape2.Text = "Shape2"  
     Me.Controls.Add(Shape1)  
     Me.Controls.Add(Shape2)  



10. Coba di run programnya, perhatikan bahwa sekarang sudah tampil 2 buah layangan di form1.




11. CCustomShape boleh dicoba ditarik dari toolbox ke Form, dengan koordinatnya diatur di Property Points pada Property Window. Tapi waktu penulis coba sih, jadinya malah error, dan setelah diutak-atik,  Penulis memutuskan untuk membuat Tutorial ini dengan langkah no 9, supaya tidak terlalu kompleks :D

Sekian 

No comments:

Post a Comment