Verlauf mit mehreren Farben mit GDI+

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 20 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Verlauf mit mehreren Farben mit GDI+

    Hey,

    ich bin zur Zeit daran eine runde Progress-Bar als Steuerelement für VB.NET Programme zu erstellen. Bislang besteht sie aus einer mittleren Ellipse und einem Bogen außen um den Fortschritt darzustellen. Dazu einige Rahmen und in der Mitte Text. Alle Farben und Schriften können über Properties geändert werden.

    Bis jetzt kann man für den Fortschritt 2 Farben für einen linearen Verlauf angeben:





    Jetzt würde ich allerdings gerne auch einen mehrfarbigen Farbverlauf dafür erstellen wie diesen hier:



    Das Problem hierbei ist, dass der Farbverlauf immer nur horizontal oder vertikal quasi im "Hintergrund" angewendet wird. Ist es möglich den entlang des Pfades verlaufen zu lassen so wie hier?

    Habe schon eine PathGradientBrush ausprobiert, aber das Ergebnis war eine Katastrophe.

    Hat jemand von euch vielleicht eine Idee?

    Danke im Vorraus!

    LG Hafreak
    Coole Sache, das hat mir sehr geholfen! Dankeschön! :)

    Das Geheimnis ist, dass die PathGradientBrush die Farben nicht gleichmäßig verteilt sondern auf die Punkte aus denen ihr Pfad besteht. Also nur so viele Punkte nehmen, wie man auch Farben hat!

    Jetzt noch eine Sache. Ich würde gerne noch den Punkt da zeichnen...leider komme ich nicht auf die Koordinaten von dem Punkt...

    und zwar wird die Ellipse mit folgendem Code gezeichnet:

    VB.NET-Quellcode

    1. G.DrawArc(P, CInt(Thickness / 2), CInt(Thickness / 2), Width - Thickness - OutterBorderWidth, Height - Thickness - OutterBorderWidth, -90, CInt((360 / _Maximum) * _Value))


    Dabei ist "Thickness" die Dicke des Fortschrittsbalkens, "Width" die Breite des Steuerelements und "OutterborderWith" die Dicke eines äußeren Randes. "Maximum" ist der Maximale Wert der erreicht werden kann (hier 100, wiel 100%) und Value halt der anzuzeigende (zwischen 0 und 100).

    Ich hab es schon durch ausrechnen versucht mit dieser Formel



    Ich bin dann auf den Punkt

    VB.NET-Quellcode

    1. Dim currentPoint as New Point(Width / 2 + ((Width / 2 - Thickness - OutterBorderWidth) + Thickness / 2) * Math.Cos(CInt(((360 / _Maximum) * _Value)) - 90), Height / 2 + ((Width / 2 - Thickness - OutterBorderWidth) + Thickness / 2) * Math.Cos(CInt(((360 / _Maximum) * _Value)) - 90))

    gekommen.
    Dabei ist der Punkt aber nur die ganze Zeit auf einer Linie von der Mitte nach außen auf einer Linie gesprungen.


    Danach damit

    stackoverflow.com/questions/54…x-y-and-startsweep-angles

    Das hab ich so übersetzt:

    VB.NET-Quellcode

    1. Public Function GetFinalPoint(StartPoint As Point, width As Double, height As Double, startAngle As Double, sweepAngle As Double) As Point
    2. Dim radius As New Point(width / 2.0, height / 2.0)
    3. Dim endAngle As Double = startAngle + sweepAngle
    4. Dim sweepdirection As Integer
    5. If sweepAngle < 0 = True Then
    6. sweepdirection = -1
    7. Else
    8. sweepdirection = 1
    9. End If
    10. 'Adjust the angles for the radius width/height
    11. startAngle = UnstretchAngle(startAngle, radius)
    12. endAngle = UnstretchAngle(startAngle, radius)
    13. 'Determine how many times to add the sweep-angel to the start-angle
    14. Dim angleMultiplier As Integer = CInt(Math.Floor(2 * sweepdirection * (endAngle - startAngle) / Math.PI) + 1)
    15. angleMultiplier = Math.Min(angleMultiplier, 4)
    16. 'Calculate the final resulting angle after sweeping
    17. Dim calculatedEndAngle As Double = startAngle + angleMultiplier * Math.PI / 2 * sweepdirection
    18. calculatedEndAngle = sweepdirection * Math.Min(sweepdirection * calculatedEndAngle, sweepdirection * endAngle)
    19. 'Calculate the final Point
    20. Return New Point((Math.Cos(calculatedEndAngle) + 1) * radius.X + StartPoint.X, (Math.Sin(calculatedEndAngle) + 1) * radius.Y + StartPoint.Y)
    21. End Function
    22. Private Function UnstretchAngle(angle As Double, radius As Point) As Double
    23. Dim radians = Math.PI * angle / 180.0
    24. If Math.Abs(Math.Cos(radians)) < 0.00001 Or Math.Abs(Math.Sin(radians)) < 0.00001 Then
    25. Return radians
    26. End If
    27. Dim stretchedAngle As Double = (Math.Atan2(Math.Sin(radians) / Math.Abs(radius.Y), Math.Cos(radians) / Math.Abs(radius.X)))
    28. Dim rotationOffset As Integer = CInt(Math.Round(radians / (2.0 * Math.PI), MidpointRounding.AwayFromZero) - CInt(Math.Round(stretchedAngle / (2.0 * Math.PI), MidpointRounding.AwayFromZero)))
    29. Return stretchedAngle + rotationOffset * Math.PI * 2.0
    30. End Function



    Der Punkt sollte dann werden

    Dim currentPoint as Point = GetFinalPoint(New Point(CInt(Thickness / 2), CInt(Thickness / 2)), Width - 2 * (Thickness + OutterBorderWidth), Height - 2 * (Thickness + OutterBorderWidth), -90, Math.Cos(CInt(((360 / _Maximum) * _Value)) - 90))

    Leider war das auch der falsche Punkt und er bewegt sich auch nicht, wenn man die "Value" verändert.

    Habt ihr irgendwelche Ideen, oder erkennt ihr meinen Fehler??

    Danke im Voraus

    LG Hafreak
    @Hafreak Sieh Dir mal den HSV-Farbraum an, das ist äquivalent zur Hue-Komponente:
    https://de.wikipedia.org/wiki/HSV-Farbraum
    mikrocontroller.net/topic/242333
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    In Ordnung, danke für den Tipp! Das ist irgendwann untergegangen...

    ich hab jetzt sowohl meine erste als auch zweite Formel umgeschrieben, leider ist das Ergebnis noch immer richtig. Bei der ersten hab ich bemerkt, dass ich bei der Y Koordinate auch Cosinus statt Sinus benutzt habe. Jetzt bewegt sich der Punkt in einem Kreis, jedoch auf einem kleineren in der oberen linken Ecke vom Original. Bei der zweiten bewegt sich immer noch überhaupt nichts, der Punkt steht starr an einer Stelle.

    Die Formeln sehen jetzt so aus:

    VB.NET-Quellcode

    1. Dim currentPoint as New Point(Width / 2 + ((Width / 2 - Thickness - OutterBorderWidth) + Thickness / 2) * Math.Cos(CInt(Math.PI / 180 * (((360 / _Maximum) * _Value) - 90))), Height / 2 + ((Width / 2 - Thickness - OutterBorderWidth) + Thickness / 2) * Math.Sin(CInt(Math.PI / 180 * (((360 / _Maximum) * _Value) - 90))))


    und

    VB.NET-Quellcode

    1. Dim currentPoint as Point = GetFinalPoint(New Point(CInt(Thickness / 2), CInt(Thickness / 2)), Width - 2 * (Thickness + OutterBorderWidth), Height - 2 * (Thickness + OutterBorderWidth), 1.5 * Math.PI, CInt(Math.PI / 180 * (((360 / _Maximum) * _Value) - 90)))


    Hier ist mir aufgefallen, dass ich hinten aus versehen, das Math.Cos beim Winkel kopiert hab. Ich sollte bei C&P echt besser aufpassen X/


    @RodFromGermany: Ich bin leidenschaftlicher Grafikdesigner und mit dem HSV Farbraum gut vertraut... Ich vermute mal dein Einwand bezieht sich auf die Sache mit der PathGradientBrush? Es stimmt, dass man einfach den Hue Wert verändern könnte, aber eine Ellipse hätte doch dennoch mehr Punkte als 360? Oder kann man das irgendwie einstellen? Oder müsste man das anders regeln? :o

    LG Hafreak
    Deine Punktberechnung sieht mir reichlich kompliziert aus

    Visual Basic-Quellcode

    1. Dim radius As Single = ((Width / 2 - Thickness - OutterBorderWidth) + Thickness / 2)
    2. Dim percent As Single = _Value / _Maximum
    3. Dim angle As Single = 2*Math.PI*_Value / _Maximum
    4. Dim currentPoint As New Point(Width\2+CInt(radius * Math.Cos(angle)),Height\2+CInt(radius * Math.Sin(angle)))

    dürfte doch wesentlich übersichtlicher sein als diese rumrechnerei mit Winkeln in grad und vorallem ergab das runden an den gesetzten Stellen keinen Sinn.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Ja, das sieht deutlich übersichtlicher aus, danke

    So sieht es im Moment aus: (Sorry für die grauenvolle Qualität....)



    Das sieht mir so aus als würde irgendwas auf die Koordinaten drauf gerechnet, was da nicht hingehört

    LG Hafreak

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Hafreak“ ()

    Also dass das ganze nicht oben Anfängt liegt natürlich erstmal daran, dass der Winkel rechts anfängt und nicht oben.
    D.h. dein Punkt ist immer um 90° zu weit vorne -> 90° abziehen bzw. 90°/180° * PI = PI /2, d.h. im Bogenmaß(was unser Winkel ja ist) einfach PI/2 abziehen.
    Die Angaben von radius/offset waren ja von dir, vlt. solltest du diese noch einmal überprüfen
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Okay, ja das hatte ich eben dann auch geändert, aber trotzdem danke :)

    Es funktioniert endlich!

    Jetzt hab ich nur das Problem, dass der Kreis bei zunehmenden Radius über des Steuerelements hinaus geht und ein Stück davon fehlt, aber das muss ich dann bei der Laufzeit abfragen und die Größe anpassen.

    Aber vielen Dank schonmal! ^^

    Sollte das Steuerelement mal fertig sein, würde ich es auch gerne mit der Community hier teilen. Gibt's da irgendwas besonderes zu beachten wenn man es online stellt?

    LG Hafreak

    Hafreak schrieb:

    Jetzt hab ich nur das Problem
    Poste mal den aktuellen Code.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Der Code schaut im Moment so aus:

    VB.NET-Quellcode

    1. Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
    2. MyBase.OnPaint(e)
    3. 'Definition of multicolor brush
    4. Dim pts() As Point = {New Point(Me.Width / 2, 0), New Point(Me.Width, 0), New Point(Me.Width, Me.Height), New Point(Me.Width / 2, Me.Height), New Point(0, Me.Height), New Point(0, 0)}
    5. Dim g_Path As New GraphicsPath
    6. g_Path.AddPolygon(pts)
    7. Dim g_brush As New PathGradientBrush(g_Path)
    8. Dim surroundColors() As Color = {Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Violet}
    9. g_brush.SurroundColors = surroundColors
    10. g_brush.CenterColor = Color.White
    11. g_brush.CenterPoint = New PointF(Me.Width / 2, Me.Height / 2)
    12. 'Create image buffer
    13. Using B As New Bitmap(Width, Height)
    14. Using G As Graphics = Graphics.FromImage(B)
    15. 'Enable anti-aliasing to prevent rough edges
    16. G.SmoothingMode = SmoothingMode.HighQuality
    17. 'Fill background color
    18. G.Clear(BackColor)
    19. 'Draw progress background
    20. Using T As New LinearGradientBrush(ClientRectangle, ProgressBackgroundColor1, ProgressBackgroundColor2, LinearGradientMode.Vertical)
    21. Using P As New Pen(T, Thickness)
    22. G.DrawArc(P, CInt(Thickness / 2), CInt(Thickness / 2), Width - Thickness - 1, Height - Thickness - 1, 0, 360)
    23. End Using
    24. End Using
    25. 'Draw progress
    26. Using g_brush ' T As New LinearGradientBrush(ClientRectangle, ProgressColor1, ProgressColor2, LinearGradientMode.Vertical)
    27. Using P As New Pen(g_brush, Thickness)
    28. P.StartCap = LineCap : P.EndCap = LineCap
    29. G.DrawArc(P, CInt(Thickness / 2), CInt(Thickness / 2), Width - Thickness - OutterBorderWidth, Height - Thickness - OutterBorderWidth, -90, CInt((360 / _Maximum) * _Value))
    30. End Using
    31. End Using
    32. 'Draw center
    33. Using T As New LinearGradientBrush(ClientRectangle, CenterColor1, CenterColor2, LinearGradientMode.Vertical)
    34. G.FillEllipse(T, Thickness + CenterMargin, Thickness + CenterMargin, Width - (Thickness + CenterMargin) * 2 - 1, Height - (Thickness + CenterMargin) * 2 - 1)
    35. End Using
    36. 'Draw progress string
    37. Dim S As SizeF = G.MeasureString(CStr(CInt((100 / _Maximum) * _Value)), Font)
    38. G.DrawString(CStr(CInt((100 / _Maximum) * _Value)), Font, New SolidBrush(HeadingColor), CInt(Width / 2 - S.Width / 2), CInt(Height / 2 - S.Height / 2))
    39. Dim S2 As SizeF = G.MeasureString("%", New Font(Font.FontFamily, Font.Size / 2))
    40. G.DrawString("%", New Font(Font.FontFamily, Font.Size / 2), New SolidBrush(CaptionColor), CInt((Width / 2 - S.Width / 2) + (S.Width - 3)), CInt(Height / 2 - S.Height / 2))
    41. 'Draw outter border
    42. G.DrawEllipse(New Drawing.Pen(OutterBorderColor, OutterBorderWidth), 0, 0, Width - 1, Height - 1)
    43. 'Draw inner border
    44. G.DrawEllipse(New Drawing.Pen(InnerBorderColor, InnerBorderWidth), Thickness + InnerBorderMargin, Thickness + InnerBorderMargin, Width - (Thickness + InnerBorderMargin) * 2 - 1, Height - (Thickness + InnerBorderMargin) * 2 - 1)
    45. 'Draw the optional circle
    46. If ShowCircle = True Then
    47. 'Calculate the point where circle must be drawn
    48. Dim radius As Single = ((Width / 2 - Thickness - OutterBorderWidth) + Thickness / 2)
    49. Dim percent As Single = _Value / _Maximum
    50. Dim angle As Single = 2 * Math.PI * _Value / _Maximum - Math.PI / 2
    51. Dim currentPoint As New Point(Width \ 2 + CInt(radius * Math.Cos(angle)), Height \ 2 + CInt(radius * Math.Sin(angle)))
    52. 'Get the color from this point
    53. Dim currentColorA As Integer = B.GetPixel(currentPoint.X, currentPoint.Y).A
    54. Dim currentColorR As Integer = B.GetPixel(currentPoint.X, currentPoint.Y).R
    55. Dim currentColorG As Integer = B.GetPixel(currentPoint.X, currentPoint.Y).G
    56. Dim currentColorB As Integer = B.GetPixel(currentPoint.X, currentPoint.Y).B
    57. Dim currentColor As Drawing.Color = Color.FromArgb(currentColorA, currentColorR, currentColorG, currentColorB)
    58. Dim currentPointRectangle As New Rectangle(currentPoint.X - (CircleRadius + CircleBorderThickness) / 2, currentPoint.Y - (CircleRadius + CircleBorderThickness) / 2, CircleRadius, CircleRadius)
    59. 'Draw the circle and its border
    60. If Value > 0 Then
    61. G.DrawEllipse(New Drawing.Pen(New SolidBrush(CircleBorderColor), CircleBorderThickness), currentPointRectangle)
    62. G.FillEllipse(New SolidBrush(currentColor), currentPointRectangle)
    63. End If
    64. End If
    65. 'Output the buffered image
    66. e.Graphics.DrawImage(B, 0, 0)
    67. End Using
    68. End Using
    69. End Sub



    Das ist das Ergebnis bei einem äußeren Rand von 1px (links) und einem von 10 (rechts):



    Die Farbe für den äußeren Rand ist hier übrigens gerade Transparent, damit halt nur der Abstand nach außen wächst...

    Ich bin auch grad etwas überfragt deswegen :o

    LG Hafreak

    Hafreak schrieb:

    Code
    Da sind etliche Variablen, die sinnvoll befüllt sein sollten.
    Und:
    Option Strict On.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Okay, ist jetzt an....heißt das eigentlich, dass man das immer anmachen sollte? Mich stört dieses Cirgendwas in meinem Lesefluss, oder seh ich das falsch? :D Ich werde mich natürlich nicht davor scheuen es anzumachen wenn es besser ist.

    Ja die ganzen Variablen sind properties die im Designer festgelegt werden. Diese sehen im konkreten Fall so aus:



    falls das denn hilft.

    LG Hafreak

    Hafreak schrieb:

    dass man das immer anmachen sollte?
    Ja.
    Weil dann dafür gesorgt wird, dass Du immer die richtigen Typen verwendest und nix dem Compiler Zufall überlässt.
    Fein, dass Du die Properties per Screenshot postest.
    Wenn Du geholfen werden willst, sorge dafür, dass der Helfende Dein Problem mit minimalem Aufwand reproduzieren kann.
    Also:
    Poste im Minimum das komplette Control und die Aufruf-Bedingungen, die Deinen Effekt reproduzieren.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Okay, entschuldigung, ich bin noch nicht sehr geübt in sowas :c

    Also hier ist das Steuerelement.

    Bei mir erscheinen Controls nach dem Einfügen irgendwie nie im Werkzeugkasten, erst nachdem ich sie einmal eingefügt und das Projekt gestartet habe:

    VB.NET-Quellcode

    1. Dim ProgressBar As New RadialBar
    2. Me.Controls.Add(ProgressBar)
    3. ProgressBar.Location = New Drawing.Point(0, 0)


    Danach ausführen, Code wieder löschen und dann sollte es ganz oben im Werkzeugkasten sein.

    Dann die Properties eingeben wie im Screenshot (dann bitte noch die Property ColorMode auf Rainbow ändern und die OutterBorderWidth auf 1) oder den Code im Designer File ändern:

    VB.NET-Quellcode

    1. Me.RadialBar1.CaptionColor = System.Drawing.Color.Silver
    2. Me.RadialBar1.CenterColor1 = System.Drawing.Color.FromArgb(CType(CType(52, Byte), Integer), CType(CType(73, Byte), Integer), CType(CType(94, Byte), Integer))
    3. Me.RadialBar1.CenterColor2 = System.Drawing.Color.FromArgb(CType(CType(85, Byte), Integer), CType(CType(122, Byte), Integer), CType(CType(154, Byte), Integer))
    4. Me.RadialBar1.CenterMargin = 7
    5. Me.RadialBar1.CircleBorderColor = System.Drawing.Color.White
    6. Me.RadialBar1.CircleBorderThickness = 3
    7. Me.RadialBar1.CircleRadius = 22
    8. Me.RadialBar1.ColorModeToUse = Beispiel.RadialBar.ColorMode.TwoColors
    9. Me.RadialBar1.Font = New System.Drawing.Font("Verdana", 14.0!)
    10. Me.RadialBar1.HeadingColor = System.Drawing.Color.White
    11. Me.RadialBar1.InnerBorderColor = System.Drawing.Color.FromArgb(CType(CType(73, Byte), Integer), CType(CType(104, Byte), Integer), CType(CType(132, Byte), Integer))
    12. Me.RadialBar1.InnerBorderMargin = 5
    13. Me.RadialBar1.InnerBorderWidth = 1.0!
    14. Me.RadialBar1.LineCap = System.Drawing.Drawing2D.LineCap.Square
    15. Me.RadialBar1.Location = New System.Drawing.Point(0, 0)
    16. Me.RadialBar1.Maximum = CType(100, Long)
    17. Me.RadialBar1.Name = "RadialBar1"
    18. Me.RadialBar1.OutterBorderColor = System.Drawing.Color.Transparent
    19. Me.RadialBar1.OutterBorderWidth = 1.0!
    20. Me.RadialBar1.ProgressBackgroundColor1 = System.Drawing.Color.LightGray
    21. Me.RadialBar1.ProgressBackgroundColor2 = System.Drawing.Color.Gainsboro
    22. Me.RadialBar1.ProgressColor1 = System.Drawing.Color.LightGreen
    23. Me.RadialBar1.ProgressColor2 = System.Drawing.Color.LimeGreen
    24. Me.RadialBar1.ShowCircle = True
    25. Me.RadialBar1.Size = New System.Drawing.Size(150, 150)
    26. Me.RadialBar1.TabIndex = 4
    27. Me.RadialBar1.Text = "RadialBar1"
    28. Me.RadialBar1.Thickness = 14
    29. Me.RadialBar1.Value = CType(81, Long)


    Wenn man jetzt die OutterBorderWidth ändert, kratzt das Teil komplett ab.

    P.S.: Wenn der Wert von dem Control über ne Animation verändert werden soll nicht den Wert ändern sondern .IncreaseValue([neuer Wert as Integer]) aufrufen

    Hafreak schrieb:

    kratzt das Teil komplett ab.
    Kann ich nicht nachvollziehen.
    Mach ein Projekt, dass diesen Effekt auf Button-Click reproduziert.
    Dieses Projekt häng als ZIP an.
    Erweiterte Antwort -> Dateianhänge -> Hochladen
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Hafreak schrieb:

    OutterBorderWidth
    Da passiert bei mir nix.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    In Ordnung, das war mein Fehler...meine Wortwahl nicht sehr klug

    Ich meinte, wenn man den OutterBorder ändert, sieht das ganze nicht mehr so aus wie es sollte. Das heißt der bunte Ring verschiebt sich nach links oben und der Kreis nach unten rechts, obwohl die einfach nur weiter nach innen gehen sollten.