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:
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
(hier werden auch Hovereffekte etc. dargestellt), sowie die
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
Ansonsten wars das im Großen und Ganzen. Weiter zur nächsten Methode:
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
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:
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:
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_
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
- ''' <summary>
- ''' Methode zum Zeichnen des Schließenknopfes.
- ''' </summary>
- Private Sub DrawCloseButton(ByVal g As Graphics, ByVal bounds As Rectangle)
- g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
- Dim r As New Rectangle(bounds.X, CInt(bounds.Y / 2), bounds.Width, bounds.Height)
- Using p As New Pen(If(r.Contains(_MouseLocation), _ActiveColor, _InActiveColor), _CloseButtonStrength)
- g.DrawLine(p, bounds.X, CInt(bounds.Y / 2), bounds.X + bounds.Width, CInt(bounds.Y / 2) + bounds.Height)
- g.DrawLine(p, bounds.X + bounds.Width, CInt(bounds.Y / 2), bounds.X, CInt(bounds.Y / 2) + bounds.Height)
- End Using
- End Sub
- ''' <summary>
- ''' Funktion zum Ermitteln des Rechtecks für den Schließenknopf.
- ''' </summary>
- Private Function GetCloseButtonRect(ByVal id As Integer) As Rectangle
- Dim tabRect As Rectangle = GetTabRect(id)
- Dim closeRect As Rectangle = New Rectangle(tabRect.Left, tabRect.Top, CloseButtonHeight, CloseButtonHeight)
- Select Case Alignment
- Case TabAlignment.Left
- closeRect.Offset((tabRect.Width - closeRect.Width) \ 2, 0)
- Case TabAlignment.Right
- closeRect.Offset((tabRect.Width - closeRect.Width) \ 2, tabRect.Height - closeRect.Height)
- Case Else
- closeRect.Offset(tabRect.Width - closeRect.Width, (tabRect.Height - closeRect.Height) \ 2)
- End Select
- Return closeRect
- 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
- ''' <summary>
- ''' Methode zum Zeichnen der Hintergründe der Tabreiter.
- ''' </summary>
- Private Sub DrawTabBackground(ByVal g As Graphics, ByVal id As Integer)
- Dim tab As Rectangle = GetTabRect(id)
- Using b As New SolidBrush(_ActiveHeaderColor)
- If id = SelectedIndex Then 'Aktiven Tabreiter zeichnen
- g.FillRectangle(b, tab)
- Using p As New Pen(_BorderColor, 1)
- If _BorderStyle = SteamTabBorderStyles.FullBorderWithTabs Then g.DrawRectangle(p, tab.X, tab.Y, tab.Width - 1, tab.Height)
- p.Color = _AccentColor
- g.DrawLine(p, tab.X + 1, tab.Y + tab.Height, tab.X + tab.Width - 2, tab.Y + tab.Height)
- End Using
- ElseIf id = HotTabIndex Then 'Ausgewählten Tabreiter zeichnen (Hover)
- Dim rc As Rectangle = tab : rc.Height += 2 : rc.Width -= 1
- g.FillRectangle(b, tab)
- Using p As New Pen(FindForm.BackColor)
- g.DrawRectangle(p, rc)
- End Using
- Else 'Inaktive Tabreiter zeichnen
- b.Color = _InActiveHeaderColor
- g.FillRectangle(b, tab)
- End If
- End Using
- ' g.FillRectangle(New SolidBrush(Color.FromArgb(50, Color.Red)), tab) 'Dev Only
- 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
- ''' <summary>
- ''' Methode zum Zeichnen des Inhaltes der Tabreiter.
- ''' </summary>
- Private Sub DrawTabContent(ByVal g As Graphics, ByVal id As Integer)
- Dim selectedOrHot As Boolean = id = Me.SelectedIndex OrElse id = Me.HotTabIndex
- Dim tabRect As Rectangle = GetTabRect(id)
- Dim closeRect As Rectangle
- 'Schließenknöpfe definieren
- If _CloseButtonLocation = SteamTabCloseButtonLocation.Center Then
- 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)
- Else
- closeRect = New Rectangle(tabRect.Right - CInt(CloseButtonHeight / 2) - _CloseButtonOffset, tabRect.Y + CloseButtonHeight + 1, CloseButtonHeight / 2, CloseButtonHeight / 2) : closeRect.Offset(-2, _CloseButtonOffset)
- End If
- Dim r As New Rectangle(tabRect.X + _CloseButtonOffset, tabRect.Y + 1, tabRect.Width - _CloseButtonOffset * 2, tabRect.Height - 1)
- 'Schließenknöpfe zeichnen
- If _DrawCloseButtons Then
- DrawCloseButton(g, closeRect)
- r = New Rectangle(tabRect.X + _CloseButtonOffset, tabRect.Y + 1, tabRect.Width - CloseButtonHeight - 1, tabRect.Height - 1)
- End If
- 'Tabreitertext zeichnen
- Using SF As New StringFormat()
- Using b As New SolidBrush(TabPages(id).ForeColor)
- Dim F As New Font(TabPages(id).Font.FontFamily, TabPages(id).Font.Size)
- Using F
- If SelectedIndex = id Then
- b.Color = _ActiveColor
- If _SelectedTabIsBold Then F = New Font(TabPages(id).Font, FontStyle.Bold)
- End If
- SF.LineAlignment = StringAlignment.Center : SF.Alignment = StringAlignment.Center
- g.DrawString(TabPages(id).Text, F, b, r, SF)
- End Using
- End Using
- End Using
- ' g.DrawRectangle(Pens.Red, r) : g.DrawRectangle(Pens.DarkRed, tabRect) 'Dev only
- 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
- 'Schatten in den Ecken zeichnen (sehr dezent)
- If (_DrawShadows) Then
- g.SmoothingMode = SmoothingMode.AntiAlias
- Dim gp As New GraphicsPath()
- gp.AddEllipse(-50, -50, Width + 100, Height + 100)
- Using pgb As New PathGradientBrush(gp) With {.CenterColor = Color.Transparent, .SurroundColors = {Color.FromArgb(75, 0, 0, 0)}}
- g.FillEllipse(pgb, -50, -50, Width + 100, Height + 100)
- End Using
- g.SmoothingMode = SmoothingMode.HighSpeed
- 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
- ''' <summary>
- ''' Erstellt einen Base64 String auf Basis eines gegebenen Bildes.
- ''' </summary>
- ''' <param name="img">Das zu konvertierende Bild.</param>
- ''' <returns>Base64 String des Bildes.</returns>
- Public Shared Function ConvertImageToString(img As Image) As String
- Using MS As New System.IO.MemoryStream
- img.Save(MS, System.Drawing.Imaging.ImageFormat.Png)
- Return Convert.ToBase64String(MS.ToArray)
- End Using
- End Function
- ''' <summary>
- ''' Erstellt ein Bild auf Basis eines gegebenen Base64 Strings.
- ''' </summary>
- ''' <param name="str">Der zu konvertierende String.</param>
- ''' <returns>Das konvertierte Bild.</returns>
- Public Shared Function ConvertStringToImage(str As String) As Image
- Try
- Using MS As New System.IO.MemoryStream(Convert.FromBase64String(str))
- Return Image.FromStream(MS)
- End Using
- Catch ex As Exception
- Return Nothing
- End Try
- 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_