Controlsammlung: SteamControls - Ein modernes DarkUI für deine Anwendung!

    • VB.NET
    • .NET (FX) 4.0

    Es gibt 6 Antworten in diesem Thema. Der letzte Beitrag () ist von FormFollowsFunction.

      Controlsammlung: SteamControls - Ein modernes DarkUI für deine Anwendung!

      Servus Leute!
      Lange hat man nichts mehr von mir gehört, und jetzt melde ich mich wieder mit einem (mini) Projekt von mir, diesmal -auf jedenfall der Anfang- direkt im Sourcecode-Austausch.
      Wie man mich kennt, habe ich mich in UI Design verliebt und verfeinere diese immer wieder mit neuen Steuerelementen.
      Es gibt unzählige Stile von Anwendungen, wobei man natürlich den standard Windowsstil nicht vergessen darf. Trotz allem finde ich außergewöhnliche, gut komponierte UI's immer wieder interessant.
      So zum Beispiel jenes des Steam Clients insbesonderen mit der Erweiterung Metro for Steam:



      Für den Anfang hab ich mich jedoch auf den Chat konzentriert. Weitere Steuerelemente folgen möglicherweise.
      Der Chat des Steam-Clients besteht ein paar wenigen Steuerelementen. Diese sind:
      Eine Picturebox für das Avatar, einer Textbox für den Chat sowie die zu sendende Nachricht, einen Button zum Senden der Nachricht sowie zum Öffnen weiterer Optionen
      und natürlich dem 'wichtigsten' Steuerelement, einem TabControl.
      Damit ihr wisst von was ich rede:



      Grundsätzlich werde ich nicht jedes Control nachbauen sondern arbeite auch schon mit Elementen die mir Windows gibt.
      Damit meine ich in diesem Fall die Textbox sowie natürlich Labels.
      Das erstellen von eigenen Textboxen ist etwas komplizierter, und in diesem Projekt komplett redundant, da das standard Steuerelement uns alles bietet was wir benötigen.
      Aufgrunddessen liegt das Hauptaugenmerkmal in diesem Thema auf dem TabControl (welches möglicherweise, und ich möchte nicht zu viel versprechen, ein eigenes Tutorial,
      in welchem mehr erklärt wird, bekommen könnte). Zusätzlich sind einzelne kleinere Steurelemente dem Projekt beigelegt, wie die AvatarBox sowie der IconButton.
      Diese sind ebenfalls interessant da diverse spannende Funktionen und Techniken eingebaut sind (besonders im Bezug auf Design!).

      Bevor ich beginne noch eine kleine Anmerkung:
      Bitte beachtet, dass dies kein Tutorial ist und sich aus gutem Grund (nur) im Sourcecode-Austausch befindet.
      Ich gehe hier nicht immer ins Detail sowie auf die Materie ein!


      Los geht's!
      Sehen wir uns zuerst das TabControl an. Grundsätzlich handelt es sich hier um ein schlichtes DarkUI Design.
      Zusätzlich jedoch hat jeder Tabreiter einen Schließenknopf. Dieser wird wie folgt gezeichnet:

      VB.NET-Quellcode

      1. ''' <summary>
      2. ''' Methode zum Zeichnen des Schließenknopfes.
      3. ''' </summary>
      4. Private Sub DrawCloseButton(ByVal g As Graphics, ByVal bounds As Rectangle)
      5. g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
      6. Dim r As New Rectangle(bounds.X, CInt(bounds.Y / 2), bounds.Width, bounds.Height)
      7. Using p As New Pen(If(r.Contains(_MouseLocation), _ActiveColor, _InActiveColor), _CloseButtonStrength)
      8. g.DrawLine(p, bounds.X, CInt(bounds.Y / 2), bounds.X + bounds.Width, CInt(bounds.Y / 2) + bounds.Height)
      9. g.DrawLine(p, bounds.X + bounds.Width, CInt(bounds.Y / 2), bounds.X, CInt(bounds.Y / 2) + bounds.Height)
      10. End Using
      11. End Sub
      12. ''' <summary>
      13. ''' Funktion zum Ermitteln des Rechtecks für den Schließenknopf.
      14. ''' </summary>
      15. Private Function GetCloseButtonRect(ByVal id As Integer) As Rectangle
      16. Dim tabRect As Rectangle = GetTabRect(id)
      17. Dim closeRect As Rectangle = New Rectangle(tabRect.Left, tabRect.Top, CloseButtonHeight, CloseButtonHeight)
      18. Select Case Alignment
      19. Case TabAlignment.Left
      20. closeRect.Offset((tabRect.Width - closeRect.Width) \ 2, 0)
      21. Case TabAlignment.Right
      22. closeRect.Offset((tabRect.Width - closeRect.Width) \ 2, tabRect.Height - closeRect.Height)
      23. Case Else
      24. closeRect.Offset(tabRect.Width - closeRect.Width, (tabRect.Height - closeRect.Height) \ 2)
      25. End Select
      26. Return closeRect
      27. End Function


      Mit der Funktion wird die Position ermittelt und mit der Methode das X gezeichnet. Beides wird später in der Paint-Methode aufgerufen.
      Die OnPaint Methode wiederum habe ich in zwei weitere Subs aufgegliedert. Die DrawTabBackground-Sub, in welcher der Hintergrund jedes Reiters gezeichnet wird
      (hier werden auch Hovereffekte etc. dargestellt), sowie die DrawTabContent-Sub in der der Text und das X des Tabreiters gezeichnet werden (oder auch nicht).

      VB.NET-Quellcode

      1. ''' <summary>
      2. ''' Methode zum Zeichnen der Hintergründe der Tabreiter.
      3. ''' </summary>
      4. Private Sub DrawTabBackground(ByVal g As Graphics, ByVal id As Integer)
      5. Dim tab As Rectangle = GetTabRect(id)
      6. Using b As New SolidBrush(_ActiveHeaderColor)
      7. If id = SelectedIndex Then 'Aktiven Tabreiter zeichnen
      8. g.FillRectangle(b, tab)
      9. Using p As New Pen(_BorderColor, 1)
      10. If _BorderStyle = SteamTabBorderStyles.FullBorderWithTabs Then g.DrawRectangle(p, tab.X, tab.Y, tab.Width - 1, tab.Height)
      11. p.Color = _AccentColor
      12. g.DrawLine(p, tab.X + 1, tab.Y + tab.Height, tab.X + tab.Width - 2, tab.Y + tab.Height)
      13. End Using
      14. ElseIf id = HotTabIndex Then 'Ausgewählten Tabreiter zeichnen (Hover)
      15. Dim rc As Rectangle = tab : rc.Height += 2 : rc.Width -= 1
      16. g.FillRectangle(b, tab)
      17. Using p As New Pen(FindForm.BackColor)
      18. g.DrawRectangle(p, rc)
      19. End Using
      20. Else 'Inaktive Tabreiter zeichnen
      21. b.Color = _InActiveHeaderColor
      22. g.FillRectangle(b, tab)
      23. End If
      24. End Using
      25. ' g.FillRectangle(New SolidBrush(Color.FromArgb(50, Color.Red)), tab) 'Dev Only
      26. End Sub

      Viel Spannendes gibt es hier nicht, ebenfalls auch nicht viel zu sagen. Hier wird lediglich der hintergrund gezeichnet, sowie bei dem ausgewählten Reiter,
      die kleine aber feine Zierlinie. Die Pen- sowie Brush-Objekte werden immer disposed um Reccourcen zu sparen.
      Ansonsten wars das im Großen und Ganzen. Weiter zur nächsten Methode:

      VB.NET-Quellcode

      1. ''' <summary>
      2. ''' Methode zum Zeichnen des Inhaltes der Tabreiter.
      3. ''' </summary>
      4. Private Sub DrawTabContent(ByVal g As Graphics, ByVal id As Integer)
      5. Dim selectedOrHot As Boolean = id = Me.SelectedIndex OrElse id = Me.HotTabIndex
      6. Dim tabRect As Rectangle = GetTabRect(id)
      7. Dim closeRect As Rectangle
      8. 'Schließenknöpfe definieren
      9. If _CloseButtonLocation = SteamTabCloseButtonLocation.Center Then
      10. closeRect = New Rectangle(tabRect.Right - CInt(CloseButtonHeight / 2) - _CloseButtonOffset, CInt(tabRect.Height / 2) + 1, CloseButtonHeight / 2, CloseButtonHeight / 2) : closeRect.Offset(-2, (tabRect.Height - closeRect.Height) \ 2)
      11. Else
      12. closeRect = New Rectangle(tabRect.Right - CInt(CloseButtonHeight / 2) - _CloseButtonOffset, tabRect.Y + CloseButtonHeight + 1, CloseButtonHeight / 2, CloseButtonHeight / 2) : closeRect.Offset(-2, _CloseButtonOffset)
      13. End If
      14. Dim r As New Rectangle(tabRect.X + _CloseButtonOffset, tabRect.Y + 1, tabRect.Width - _CloseButtonOffset * 2, tabRect.Height - 1)
      15. 'Schließenknöpfe zeichnen
      16. If _DrawCloseButtons Then
      17. DrawCloseButton(g, closeRect)
      18. r = New Rectangle(tabRect.X + _CloseButtonOffset, tabRect.Y + 1, tabRect.Width - CloseButtonHeight - 1, tabRect.Height - 1)
      19. End If
      20. 'Tabreitertext zeichnen
      21. Using SF As New StringFormat()
      22. Using b As New SolidBrush(TabPages(id).ForeColor)
      23. Dim F As New Font(TabPages(id).Font.FontFamily, TabPages(id).Font.Size)
      24. Using F
      25. If SelectedIndex = id Then
      26. b.Color = _ActiveColor
      27. If _SelectedTabIsBold Then F = New Font(TabPages(id).Font, FontStyle.Bold)
      28. End If
      29. SF.LineAlignment = StringAlignment.Center : SF.Alignment = StringAlignment.Center
      30. g.DrawString(TabPages(id).Text, F, b, r, SF)
      31. End Using
      32. End Using
      33. End Using
      34. ' g.DrawRectangle(Pens.Red, r) : g.DrawRectangle(Pens.DarkRed, tabRect) 'Dev only
      35. End Sub


      Ebenfalls eigentlich sehr einfach. Grundsätzlich ist diese nur so lang, da viel anpassbar ist.
      Wenn man nur das grundsätzliche Design haben möchte, ohne Anpassmöglichkeiten (Stichwort Eigenschaften) wäre diese Methode sowie das ganze Projekt wesentlich kürzer.
      Nachdem Zeichnen des X's zum schließen des Tabreiters, iwrd nurnoch die Beschriftung der Reiter gezeichnet.
      Nun muss nurnoch der Hintergrund des ganzen Controls, sowie in diesem Fall die Umrandung gezeichnet und die zwei oben genannten Methoden aufgerufen werden.
      Natürlich haben die Schließenknöpfe auch noch keine Funktion und werden mittelns InteropServices zum Leben erweckt.
      Mehr dazu jedoch im Sourcecode.

      Diverses
      Nun möchte ich noch diverse Snippets hervorheben die ich doch ganz interessant finde:
      So zum Beispiel bei der AvatarBox. Ich gehe sehr gerne ins Detail bei Sachen Design. Deswegen ist mir aufgefallen,
      dass im Steam-Client die Boxen mit den Avataren in den Ecken einen kleinen Schatten-Effekt haben. Diesen wollte ich natürlich nachbauen.
      Dazu verwendet man im Normalfall einen Radial-Gradient-Brush. Dieser ist aber (unter diesem Namen, sofern ich mich nicht täusche) nicht verfügbar.
      Also habe ich es wie folgt gelöst:

      VB.NET-Quellcode

      1. 'Schatten in den Ecken zeichnen (sehr dezent)
      2. If (_DrawShadows) Then
      3. g.SmoothingMode = SmoothingMode.AntiAlias
      4. Dim gp As New GraphicsPath()
      5. gp.AddEllipse(-50, -50, Width + 100, Height + 100)
      6. Using pgb As New PathGradientBrush(gp) With {.CenterColor = Color.Transparent, .SurroundColors = {Color.FromArgb(75, 0, 0, 0)}}
      7. g.FillEllipse(pgb, -50, -50, Width + 100, Height + 100)
      8. End Using
      9. g.SmoothingMode = SmoothingMode.HighSpeed
      10. End If

      Ich erstellle einen Graphicpath der eine Ellipse ist (in diesem Fall ein Kreis). Der Mittelpunkt liegt in dem der AvatarBox.
      Die Ellipse ist um ein Vielfaches größer als diese. Nun wird ein radialer Verlauf von Transparent zu einem leicht sichtbaren Schwarz gezeichnen.
      Und tadaa! der Schatteneffekt ist vollendet.

      Ebenfalls spannend finde ich die eine Funktion um Bilder in einen String zu konvertieren beziehungsweise umgekehrt.
      Ich verwende diese bei dem IconButton-Steuerelemente um das standard Icon sowie ein weiteres im Sourcecode anzugeben ohne es in das Projekt einbinden zu müssen.
      Dazu verwende ich folgende Funktionen:

      VB.NET-Quellcode

      1. ''' <summary>
      2. ''' Erstellt einen Base64 String auf Basis eines gegebenen Bildes.
      3. ''' </summary>
      4. ''' <param name="img">Das zu konvertierende Bild.</param>
      5. ''' <returns>Base64 String des Bildes.</returns>
      6. Public Shared Function ConvertImageToString(img As Image) As String
      7. Using MS As New System.IO.MemoryStream
      8. img.Save(MS, System.Drawing.Imaging.ImageFormat.Png)
      9. Return Convert.ToBase64String(MS.ToArray)
      10. End Using
      11. End Function
      12. ''' <summary>
      13. ''' Erstellt ein Bild auf Basis eines gegebenen Base64 Strings.
      14. ''' </summary>
      15. ''' <param name="str">Der zu konvertierende String.</param>
      16. ''' <returns>Das konvertierte Bild.</returns>
      17. Public Shared Function ConvertStringToImage(str As String) As Image
      18. Try
      19. Using MS As New System.IO.MemoryStream(Convert.FromBase64String(str))
      20. Return Image.FromStream(MS)
      21. End Using
      22. Catch ex As Exception
      23. Return Nothing
      24. End Try
      25. End Function


      Bilder werden hierbei in einen Base64-String konvertiert, und zurück.
      Im Sourcecode sind die Strings enthalten welche ebenfalls vom Steuerelement verwendet werden.


      Das Ergebnis
      Folgendes kam als Ergebnis dabei heraus:



      Natürlich ist es keine 1zu1 Kopie. Dennoch finde ich (besonders wenn man noch mehr Zeit hineinsteckt) kommt es sehr nah an das Original ran.
      Wie oben schon erwähnt, folgt möglicherweise mehr.
      Das Projekt dient zum Lernen kann aber auch gerne weiterentwickelt werden. Ich werde vermutlich das Selbe machen.
      Seht euch einfach den beigelegten Sourcecode an und werden schlauer.
      Bei Fragen und Anregungen stehe ich natürlich zur Verfügung.

      Man hört sich beim nächsten Projekt!
      Liebe Grüße und viel Spaß!

      Martin Pfeiffer
      aka. Gather_
      Dateien
      Mfg: Gather
      Private Nachrichten bezüglich VB-Fragen werden Ignoriert!


      @FantaZimt: Ja könntest du, indem du die WndProc Methode erweiterst:

      VB.NET-Quellcode

      1. Private Const TCM_ADJUSTRECT As Integer = &H1328
      2. Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
      3. If m.Msg = TCM_SETPADDING Then
      4. m.LParam = MAKELPARAM(Me.Padding.X + CloseButtonHeight \ 2, Me.Padding.Y)
      5. End If
      6. If m.Msg = WM_MOUSEDOWN AndAlso Not Me.DesignMode Then
      7. If _DrawCloseButtons Then
      8. Dim pt As Point = Me.PointToClient(Cursor.Position)
      9. Dim closeRect As Rectangle = GetCloseButtonRect(HotTabIndex)
      10. If closeRect.Contains(pt) Then
      11. TabPages.RemoveAt(HotTabIndex)
      12. m.Msg = WM_NULL
      13. End If
      14. End If
      15. End If
      16. If m.Msg = TCM_ADJUSTRECT AndAlso Not DesignMode Then
      17. m.Result = CType(1, IntPtr)
      18. Return
      19. End If
      20. MyBase.WndProc(m)
      21. End Sub


      Sieht dann so aus:


      Dies bezüglich sieh dir folgenden Post von @Trade an:
      TabControl ohne Reiter
      Trotzdem meine Frage an dich:
      Was ist der Sinn dahinter in einem selbst designten Steuerelement die Reiter zu entfernen? Dann kannst du doch gleich das Standardcontrol verwenden, und einfach die BackColor Eigenschaft verändern...

      @asusdk:
      Vielen Dank!
      Mfg: Gather
      Private Nachrichten bezüglich VB-Fragen werden Ignoriert!


      @Gather da hat @FantaZimt leider Vollkommen recht, ich hatte mir damals auch gedacht wäre ja genial wenn das ginge, und (ich weis leider nicht mehr wer) hat hier im Forum auch eine Anleitung geschrieben wie es geht, leider ist dann oben Links dennoch die Linie verrutscht und das sieht sehr unschön aus, man kann zwar ein anderes Control drüberlegen (z.B. ein Panel) aber das ist dann nur eine "billige" retusche dieses Problems, ich hab mich schon mehrmals dran versucht aber für ein Usercontrol das quasi wirklich ein Tabfreies tabcontrol darstellt hab ich noch nicht genug erfahrung
      If Energy = Low Then
      Drink(aHugeCoffee)
      Else
      Drink(aHugeCoffeeToo)
      End If

      asusdk schrieb:

      @Gather da hat @FantaZimt leider Vollkommen recht, ...

      Negative ! Ich nutze öffters mal ein TablessTabControl und habe keine Probleme, mit unschöhnen Linien (Siehe Anhang).
      Dateien