I'm trying to set up a progressbar with a picturebox, it has 4 colors and has rounded corners and centers. I've already managed to set up the picturebox with the colors and rounded corners, but I can't make the center.
This is an example of what I'm trying to reproduce:
This is what I've already managed to do:
This is the code that I'm using:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim pictureBox As New PictureBox()
pictureBox.Size = New Size(280, 30)
pictureBox.Location = New Point(10, 80)
AddHandler pictureBox.Paint, AddressOf Me.PictureBox_Paint
Me.Controls.Add(pictureBox)
End Sub
Private Sub PictureBox_Paint(sender As Object, e As PaintEventArgs)
Dim g As Graphics = e.Graphics
g.SmoothingMode = SmoothingMode.AntiAlias ' Essencial para bordas suaves
Dim pb As PictureBox = CType(sender, PictureBox)
Dim area As Rectangle = pb.ClientRectangle
Dim colors() As Color = {
Color.FromArgb(85, 85, 135),
Color.FromArgb(217, 83, 79),
Color.FromArgb(240, 173, 78),
Color.FromArgb(91, 192, 222)
}
Dim proportions() As Double = {0.25, 0.15, 0.35, 0.25}
Dim totalWidth As Single = area.Width
Dim totalHeight As Single = area.Height
Dim cornerRadius As Single = CSng(Math.Min(totalWidth * 0.1, totalHeight / 2.0))
If cornerRadius <= 0 Then cornerRadius = 1
Dim currentX As Single = area.X
Dim numSegments As Integer = colors.Length
For i As Integer = 0 To numSegments - 1
Dim segmentWidth As Single
If i = numSegments - 1 Then
segmentWidth = (area.X + totalWidth) - currentX
Else
segmentWidth = CSng(totalWidth * proportions(i))
End If
If segmentWidth <= 0 Then Continue For
Dim segmentRect As New RectangleF(currentX, area.Y, segmentWidth, totalHeight)
Dim roundLeft As Boolean = (i = 0)
Dim roundRight As Boolean = (i = numSegments - 1)
Using segmentPath As GraphicsPath = CreateSegmentPath(segmentRect, cornerRadius, roundLeft, roundRight)
Using segmentBrush As New SolidBrush(colors(i))
g.FillPath(segmentBrush, segmentPath)
End Using
End Using
currentX += segmentWidth
Next
End Sub
Private Function CreateSegmentPath(rectF As RectangleF, radius As Single, makeLeftRound As Boolean, makeRightRound As Boolean) As GraphicsPath
Dim path As New GraphicsPath()
Dim diameter As Single = Math.Max(0, radius * 2)
If diameter > rectF.Width Then diameter = rectF.Width
If diameter > rectF.Height Then diameter = rectF.Height
radius = diameter / 2.0F
Dim Left As Single = rectF.Left
Dim Top As Single = rectF.Top
Dim Right As Single = rectF.Right
Dim Bottom As Single = rectF.Bottom
If radius <= 0 OrElse rectF.Width <= 0 OrElse rectF.Height <= 0 Then
path.AddRectangle(rectF)
Return path
End If
If makeLeftRound Then
path.AddArc(Left, Top, diameter, diameter, 180, 90)
Else
' Começa com uma linha reta no canto
path.AddLine(Left, Top + radius, Left, Top)
path.AddLine(Left, Top, Left + radius, Top)
End If
path.AddLine(Left + radius, Top, Right - radius, Top)
If makeRightRound Then
path.AddArc(Right - diameter, Top, diameter, diameter, 270, 90)
Else
path.AddLine(Right - radius, Top, Right, Top)
path.AddLine(Right, Top, Right, Top + radius)
End If
path.AddLine(Right, Top + radius, Right, Bottom - radius)
If makeRightRound Then
path.AddArc(Right - diameter, Bottom - diameter, diameter, diameter, 0, 90)
Else
path.AddLine(Right, Bottom - radius, Right, Bottom) ' Termina a linha direita
path.AddLine(Right, Bottom, Right - radius, Bottom) ' Começa a linha inferior
End If
path.AddLine(Right - radius, Bottom, Left + radius, Bottom)
If makeLeftRound Then
path.AddArc(Left, Bottom - diameter, diameter, diameter, 90, 90)
Else
path.AddLine(Left + radius, Bottom, Left, Bottom)
path.AddLine(Left, Bottom, Left, Bottom - radius)
End If
path.CloseFigure()
Return path
End Function
I appreciate any help to solve this issue
Update my code:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim pictureBox As New PictureBox()
pictureBox.Size = New Size(280, 30)
pictureBox.Location = New Point(10, 80)
AddHandler pictureBox.Paint, AddressOf Me.PictureBox_Paint
Me.Controls.Add(pictureBox)
End Sub
Private Sub PictureBox_Paint(sender As Object, e As PaintEventArgs)
Dim g As Graphics = e.Graphics
g.SmoothingMode = SmoothingMode.AntiAlias
Dim pb As PictureBox = CType(sender, PictureBox)
Dim area As Rectangle = pb.ClientRectangle
Dim colors As New List(Of Color) From {
Color.FromArgb(70, 71, 117), ' Produtivo
Color.FromArgb(210, 50, 45), ' Não Produtivo
Color.FromArgb(237, 156, 40), ' Atenção
Color.FromArgb(91, 192, 222) ' Não Definido
}
Dim proportions As New List(Of Double) From {0.25, 0.15, 0.35, 0.25}
Dim totalWidth As Single = area.Width
Dim totalHeight As Single = area.Height
Dim cornerRadius As Single = CSng(Math.Min(totalWidth * 0.1, totalHeight / 2.0))
If cornerRadius <= 0 Then cornerRadius = 1
Dim currentX As Single = area.X
paths.Clear() ' Limpar a lista antes de adicionar novos caminhos
' Criar os caminhos para cada segmento
For i As Integer = 0 To colors.Count - 1
Dim segmentWidth As Single = CSng(totalWidth * proportions(i))
If segmentWidth <= 0 Then Continue For
Dim segmentRect As New RectangleF(currentX, area.Y, segmentWidth, totalHeight)
Dim roundLeft As Boolean = (i = 0)
Dim roundRight As Boolean = (i = colors.Count - 1)
Dim segmentPath As GraphicsPath = CreateSegmentPath(segmentRect, cornerRadius, roundLeft, roundRight)
paths.Add(segmentPath)
currentX += segmentWidth
Next
' Pintar os caminhos de trás para frente
For i As Integer = paths.Count - 1 To 0 Step -1
Using segmentBrush As New SolidBrush(colors(i))
g.FillPath(segmentBrush, paths(i))
End Using
Next
End Sub
Private Function CreateSegmentPath(rectF As RectangleF, radius As Single, makeLeftRound As Boolean, makeRightRound As Boolean) As GraphicsPath
Dim path As New GraphicsPath()
Dim diameter As Single = Math.Max(0, radius * 2)
If diameter > rectF.Width Then diameter = rectF.Width
If diameter > rectF.Height Then diameter = rectF.Height
radius = diameter / 2.0F
Dim Left As Single = rectF.Left
Dim Top As Single = rectF.Top
Dim Right As Single = rectF.Right
Dim Bottom As Single = rectF.Bottom
If radius <= 0 OrElse rectF.Width <= 0 OrElse rectF.Height <= 0 Then
path.AddRectangle(rectF)
Return path
End If
' --- Construção do Caminho ---
' Canto Superior Esquerdo
If makeLeftRound Then
path.AddArc(Left, Top, diameter, diameter, 180, 90)
Else
' Começa com uma linha reta no canto
path.AddLine(Left, Top + radius, Left, Top) ' Garante o ponto inicial correto
path.AddLine(Left, Top, Left + radius, Top)
End If
' Linha Superior (entre os cantos/arcos)
path.AddLine(Left + radius, Top, Right - radius, Top)
' Canto Superior Direito
If makeRightRound Then
path.AddArc(Right - diameter, Top, diameter, diameter, 270, 90)
Else
path.AddLine(Right - radius, Top, Right, Top) ' Termina a linha superior
path.AddLine(Right, Top, Right, Top + radius) ' Começa a linha direita
End If
' Linha Direita (Vertical)
path.AddLine(Right, Top + radius, Right, Bottom - radius)
' Canto Inferior Direito
If makeRightRound Then
path.AddArc(Right - diameter, Bottom - diameter, diameter, diameter, 0, 90)
Else
path.AddLine(Right, Bottom - radius, Right, Bottom) ' Termina a linha direita
path.AddLine(Right, Bottom, Right - radius, Bottom) ' Começa a linha inferior
End If
' Linha Inferior
path.AddLine(Right - radius, Bottom, Left + radius, Bottom)
' Canto Inferior Esquerdo
If makeLeftRound Then
path.AddArc(Left, Bottom - diameter, diameter, diameter, 90, 90)
Else
path.AddLine(Left + radius, Bottom, Left, Bottom) ' Termina a linha inferior
path.AddLine(Left, Bottom, Left, Bottom - radius) ' Começa a linha esquerda (para fechar)
End If
path.CloseFigure() ' Fecha o caminho conectando a última linha ao início
Return path
End Function
Reverse the color array to fill the larger value segments first. The bluish color represents the largest segment and therefore it fills the entire area. The value of the goldish color represents 75% of the width (0.25 + 0.15 + 0.35). Drawing it over the first segment leaves 25% only from its area visible. And so on for the rest of the smaller segments.
In the loop, you don't need to offset the locations. Simply, take from the area.Width
the width of each segment to create a rectangle and pass it to the path method to create a rectangular path with all sides rounded.
Here's a custom control.
Public Class SegmentsProgressBar
Inherits PictureBox
<Flags()>
Friend Enum Corner As Integer
None = 0
TopLeft = 1
BottomLeft = 2
TopRight = 4
BottomRight = 8
All = TopLeft Or BottomLeft Or TopRight Or BottomRight
End Enum
Sub New()
ResizeRedraw = True
End Sub
Protected Overrides Sub OnPaint(e As PaintEventArgs)
MyBase.OnPaint(e)
Dim area As Rectangle = ClientRectangle
Dim colors As Color() = {
Color.FromArgb(91, 192, 222),
Color.FromArgb(240, 173, 78),
Color.FromArgb(217, 83, 79),
Color.FromArgb(85, 85, 135)
}
Dim proportions As Single() = {1, 0.75, 0.4, 0.25}
Dim cornerRadius As Single = Math.Max(1, CSng(Math.Min(area.Width * 0.1, area.Height / 2.0)))
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half
For i As Integer = 0 To proportions.Length - 1
Dim segmentWidth As Single = area.Width * proportions(i)
If segmentWidth <= 0 Then Continue For
Dim segmentRect As New RectangleF(area.X, area.Y, segmentWidth, area.Height)
Using segmentPath As GraphicsPath = CreatePath(segmentRect, cornerRadius, Corner.All),
segmentBrush As New SolidBrush(colors(i))
e.Graphics.FillPath(segmentBrush, segmentPath)
End Using
Next
End Sub
Private Function CreatePath(rectF As RectangleF, radius As Single, corners As Corner) As GraphicsPath
Dim path As New GraphicsPath()
Dim diameter As Single = Math.Max(0, radius * 2)
If diameter > rectF.Width Then diameter = rectF.Width
If diameter > rectF.Height Then diameter = rectF.Height
radius = diameter / 2.0F
Dim Left As Single = rectF.Left
Dim Top As Single = rectF.Top
Dim Right As Single = rectF.Right
Dim Bottom As Single = rectF.Bottom
If radius <= 0 OrElse corners = Corner.None Then
path.AddRectangle(rectF)
Return path
End If
If (corners And Corner.TopLeft) > 0 Then
path.AddArc(Left, Top, diameter, diameter, 180, 90)
Else
path.AddLine(Left, Top, Left, Top)
End If
If (corners And Corner.TopRight) > 0 Then
path.AddArc(Right - diameter, Top, diameter, diameter, 270, 90)
Else
path.AddLine(Right, Top, Right, Top)
End If
If (corners And Corner.BottomRight) > 0 Then
path.AddArc(Right - diameter, Bottom - diameter, diameter, diameter, 0, 90)
Else
path.AddLine(Right, Bottom, Right, Bottom)
End If
If (corners And Corner.BottomLeft) > 0 Then
path.AddArc(Left, Bottom - diameter, diameter, diameter, 90, 90)
Else
path.AddLine(Left, Bottom, Left, Bottom)
End If
path.CloseFigure()
Return path
End Function
End Class
Rebuild, find the SegmentsProgressBar
at the top of the Toolbox window, and drop an instance.