Erstellen eines einfachen Tracker-Controls

    • VB.NET

    Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von Gather.

      Erstellen eines einfachen Tracker-Controls

      Hallo Community.

      In diesem Artikel erkläre ich, wie man ein Trackercontrol (bekannt aus z.B. dem Taskmanager) erstellt, und erkläre dabei auch allgemein die Vorgehensweise, die man bei so etwas verwenden sollte.
      Ich habe das Control ursprünglich in C# erstellt, weswegen ich immer beide Codes posten werde. Allerdings braucht ihr euch keine Sorgen zu machen, ich habe alles per Hand übersetzt, weshalb der VB-Code qualitativ genauso hochwertig ist.


      Aber nun zum Eigentlichen. Bevor wir mit dem Programmieren anfangen können, sollten wir uns zunächst überlegen, was unser Control alles können soll. Ich verwende dazu sehr gerne ein Klassendiagramm, dieses bekommt ihr mit "Rechtstklick auf euer Projekt im Projektmappenexplorer -> Klassendiagramm anzeigen".
      Das Trackercontrol soll mehrere Pfade besitzen können (also mehrere Graphen-Linien), also liegt es nahe, daraus eine extra Klasse zu machen. Dort können wird dann alle Eigenschaft gut unterbringen. In meinem Fall möchte ich, dass ein Pfad seine, Farbe, seine Linienbreite, seine Füllfarbe, ob er überhaupt gefüllt ist, den maximalen Wert, einen Namen, und natürlich die später angezeigten Werte speichert. Das Klassendiagramm für die Pfad-Klasse sieht dann so aus:

      Die restlichen Eigenschaften und Methoden, die ihr seht, kommen von den implementierten Interfaces, zu denen ich nun auch etwas sagen werden.
      Bei einem Pfad handelt es sich um eine Liste von Werten, die nachher im Tracker angezeigt werden, aus diesen wird das Diagramm zusammengesetzt. Also liegt es nahe, dass unsere Klasse IEnumerable und ICollection implementiert. Ich habe mich dafür entschieden, dass die Werte als Integer gespeichert werden sollen, deswegen IEnumerable(Of Integer). Ihr könnt natürlich auch einen anderen Datentypen wie Single oder Double wählen.
      Auch INotifyPropertyChanged habe ich implementiert, denn wir wollen das Tracker-Control später ja auch darüber informieren, wenn sich eine Eigenschaft geändert hat und neugezeichnet werden muss. Mehr dazu gibts dann gleich beim Code.
      Zur Architektur kann ich sagen, ich habe absichtlich nicht einfach IList(Of Integer) implementiert, da diese auch Methoden wie Add und Remove besitzt. Diese möchte ich aber nicht, denn man soll immer nur ganz hinten einen neuen Wert hinzufügen können, und man soll keine Werte entfernen können. Außerdem habe ich noch eine Eigenschaft "MaxStoredValues" angelegt, die angibt, wie viele Werte der Pfad speichern soll. Wird die Anzahl überschritten, so soll die Klasse die überflüssigen Werte löschen. Dadurch verhindere ich, dass irgendwann ein paar Millionen Werte gespeichert sind, die man sowieso nicht sehen kann.

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

      Erstellen eines einfachen Tracker-Controls - Fortsetzung 1

      Jetzt gehts ans Eingemachte, wir müssen nun alles, was wir uns im Klassendiagramm zusammengebastelt haben, implementieren. Ich poste nun zuerst den fertigen Code und erkläre ihn dann.

      VB

      VB.NET-Quellcode

      1. Imports System.Drawing
      2. Imports System.ComponentModel
      3. ''' <summary>
      4. ''' Ein Pfad, der in einem Tracker angezeigt werden kann.
      5. ''' </summary>
      6. ''' <remarks></remarks>
      7. Public Class TrackerPath : Implements IEnumerable(Of Integer), ICollection, INotifyPropertyChanged
      8. Private values As Queue(Of Integer)
      9. Private maxStored As Integer
      10. Private _lineColor As Color
      11. Private _fillColor As Color
      12. Private _isFilled As Boolean
      13. Private _lineWidth As Single
      14. Private _name As String
      15. Private _maximum As Integer
      16. Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
      17. ''' <summary>
      18. ''' Der größte Wert, der im Tracker angezeigt wird.
      19. ''' Es können auch höhere Werte hinzugefügt werden.
      20. ''' </summary>
      21. ''' <remarks></remarks>
      22. Public Property Maximum As Integer
      23. Get
      24. Return _maximum
      25. End Get
      26. Set(value As Integer)
      27. If (_maximum <> value) Then
      28. _maximum = value
      29. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Maximum"))
      30. End If
      31. End Set
      32. End Property
      33. ''' <summary>
      34. ''' Der Name dieses Pfades.
      35. ''' </summary>
      36. ''' <value></value>
      37. ''' <returns></returns>
      38. ''' <remarks></remarks>
      39. Public Property Name As String
      40. Get
      41. Return _name
      42. End Get
      43. Set(value As String)
      44. If (_name <> value) Then
      45. _name = value
      46. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Name"))
      47. End If
      48. End Set
      49. End Property
      50. ''' <summary>
      51. ''' Die Farbe dieses Pfades.
      52. ''' </summary>
      53. ''' <value></value>
      54. ''' <returns></returns>
      55. ''' <remarks></remarks>
      56. Public Property LineColor As Color
      57. Get
      58. Return _lineColor
      59. End Get
      60. Set(value As Color)
      61. If (_lineColor <> value) Then
      62. _lineColor = value
      63. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("LineColor"))
      64. End If
      65. End Set
      66. End Property
      67. ''' <summary>
      68. ''' Die Farbe der Füllung des Pfades.
      69. ''' </summary>
      70. ''' <value></value>
      71. ''' <returns></returns>
      72. ''' <remarks></remarks>
      73. Public Property FillColor As Color
      74. Get
      75. Return _fillColor
      76. End Get
      77. Set(value As Color)
      78. If (_fillColor <> value) Then
      79. _fillColor = value
      80. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("FillColor"))
      81. End If
      82. End Set
      83. End Property
      84. ''' <summary>
      85. ''' Gibt an, ob dieser Pfad gefüllt sein soll.
      86. ''' </summary>
      87. ''' <value></value>
      88. ''' <returns></returns>
      89. ''' <remarks></remarks>
      90. Public Property IsFilled As Boolean
      91. Get
      92. Return _isFilled
      93. End Get
      94. Set(value As Boolean)
      95. If (_isFilled <> value) Then
      96. _isFilled = value
      97. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("IsFilled"))
      98. End If
      99. End Set
      100. End Property
      101. ''' <summary>
      102. ''' Die Linienbreite dieses Pfades.
      103. ''' </summary>
      104. ''' <value></value>
      105. ''' <returns></returns>
      106. ''' <remarks></remarks>
      107. Public Property LineWidth As Single
      108. Get
      109. Return _lineWidth
      110. End Get
      111. Set(value As Single)
      112. If (_lineWidth <> value) Then
      113. _lineWidth = value
      114. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("LineWidth"))
      115. End If
      116. End Set
      117. End Property
      118. ''' <summary>
      119. ''' Gibt die maximale Anzahl an Werten an, die in diesem Pfad gespeichert werden.
      120. ''' </summary>
      121. ''' <value></value>
      122. ''' <returns></returns>
      123. ''' <remarks></remarks>
      124. Public Property MaxStoredValues As Integer
      125. Get
      126. Return maxStored
      127. End Get
      128. Set(value As Integer)
      129. If maxStored <> value Then
      130. maxStored = value
      131. If maxStored < values.Count Then
      132. CutQueue()
      133. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Items"))
      134. End If
      135. End If
      136. End Set
      137. End Property
      138. ''' <summary>
      139. ''' Erstellt einen neuen Pfad.
      140. ''' </summary>
      141. ''' <remarks></remarks>
      142. Public Sub New()
      143. values = New Queue(Of Integer)()
      144. maxStored = 100
      145. _maximum = 100
      146. _lineWidth = 1
      147. _isFilled = False
      148. End Sub
      149. Private Sub CutQueue()
      150. Do While values.Count > maxStored
      151. values.Dequeue()
      152. Loop
      153. End Sub
      154. ''' <summary>
      155. ''' Fügt diesem Pfad einen neuen Wert hinzu.
      156. ''' </summary>
      157. ''' <param name="value"></param>
      158. ''' <remarks></remarks>
      159. Public Sub Add(value As Integer)
      160. values.Enqueue(value)
      161. If (maxStored < values.Count) Then CutQueue()
      162. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Items"))
      163. End Sub
      164. Public Function GetEnumerator() As IEnumerator(Of Integer) Implements IEnumerable(Of Integer).GetEnumerator
      165. Return values.GetEnumerator()
      166. End Function
      167. Public Sub CopyTo(array As Integer(), index As Integer)
      168. values.CopyTo(array, index)
      169. End Sub
      170. Public Sub CopyTo(array As Array, index As Integer) Implements ICollection.CopyTo
      171. values.CopyTo(DirectCast(array, Integer()), index)
      172. End Sub
      173. Public ReadOnly Property Count As Integer Implements ICollection.Count
      174. Get
      175. Return values.Count
      176. End Get
      177. End Property
      178. Public ReadOnly Property IsSynchronized As Boolean Implements ICollection.IsSynchronized
      179. Get
      180. Return False
      181. End Get
      182. End Property
      183. Public ReadOnly Property SyncRoot As Object Implements ICollection.SyncRoot
      184. Get
      185. Return DirectCast(values, ICollection).SyncRoot
      186. End Get
      187. End Property
      188. Private Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
      189. Return values.GetEnumerator()
      190. End Function
      191. End Class
      C#

      C-Quellcode

      1. using System;
      2. using System.Collections;
      3. using System.Collections.Generic;
      4. using System.Linq;
      5. using System.Text;
      6. using System.ComponentModel;
      7. using System.Drawing;
      8. using System.Drawing.Drawing2D;
      9. namespace TrackerControl
      10. {
      11. /// <summary>
      12. /// Ein Pfad, der in einem Tracker angezeigt werden kann.
      13. /// </summary>
      14. public class TrackerPath : IEnumerable<int>, ICollection, INotifyPropertyChanged
      15. {
      16. Queue<int> values;
      17. int maxStored;
      18. Color lineColor;
      19. Color fillColor;
      20. bool isFilled;
      21. Single lineWidth;
      22. string name;
      23. int maximum;
      24. public event PropertyChangedEventHandler PropertyChanged;
      25. /// <summary>
      26. /// Der größte Wert, der im Tracker angezeigt wird.
      27. /// Es können auch höhere Werte hinzugefügt werden.
      28. /// </summary>
      29. public int Maximum
      30. {
      31. get
      32. {
      33. return maximum;
      34. }
      35. set
      36. {
      37. if (maximum != value)
      38. return;
      39. maximum = value;
      40. if (PropertyChanged != null)
      41. PropertyChanged(this, new PropertyChangedEventArgs("Maximum"));
      42. }
      43. }
      44. /// <summary>
      45. /// Der Name dieses Pfades.
      46. /// </summary>
      47. public string Name
      48. {
      49. get
      50. {
      51. return name;
      52. }
      53. set
      54. {
      55. if (name == value)
      56. return;
      57. name = value;
      58. if (PropertyChanged != null)
      59. PropertyChanged(this, new PropertyChangedEventArgs("Name"));
      60. }
      61. }
      62. /// <summary>
      63. /// Die Farbe dieses Pfades.
      64. /// </summary>
      65. public Color LineColor
      66. {
      67. get
      68. {
      69. return lineColor;
      70. }
      71. set
      72. {
      73. if (lineColor == value)
      74. return;
      75. lineColor = value;
      76. if (PropertyChanged != null)
      77. PropertyChanged(this, new PropertyChangedEventArgs("LineColor"));
      78. }
      79. }
      80. /// <summary>
      81. /// Die Farbe der Füllung des Pfades.
      82. /// </summary>
      83. public Color FillColor
      84. {
      85. get
      86. {
      87. return fillColor;
      88. }
      89. set
      90. {
      91. if (fillColor == value)
      92. return;
      93. fillColor = value;
      94. if (PropertyChanged != null)
      95. PropertyChanged(this, new PropertyChangedEventArgs("FillColor"));
      96. }
      97. }
      98. /// <summary>
      99. /// Gibt an, ob dieser Pfad gefüllt sein soll.
      100. /// </summary>
      101. public bool IsFilled
      102. {
      103. get
      104. {
      105. return isFilled;
      106. }
      107. set
      108. {
      109. if (isFilled == value)
      110. return;
      111. isFilled = value;
      112. if (PropertyChanged != null)
      113. PropertyChanged(this, new PropertyChangedEventArgs("IsFilled"));
      114. }
      115. }
      116. /// <summary>
      117. /// Die Linienbreite dieses Pfades.
      118. /// </summary>
      119. public Single LineWidth
      120. {
      121. get
      122. {
      123. return lineWidth;
      124. }
      125. set
      126. {
      127. if (lineWidth == value)
      128. return;
      129. lineWidth = value;
      130. if (PropertyChanged != null)
      131. PropertyChanged(this, new PropertyChangedEventArgs("LineWidth"));
      132. }
      133. }
      134. /// <summary>
      135. /// Gibt die maximale Anzahl an Werten an, die in diesem Pfad gespeichert werden.
      136. /// </summary>
      137. public int MaxStoredValues
      138. {
      139. get
      140. {
      141. return maxStored;
      142. }
      143. set
      144. {
      145. if (maxStored == value)
      146. return;
      147. maxStored = value;
      148. if (maxStored < values.Count)
      149. {
      150. CutQueue();
      151. if (PropertyChanged != null)
      152. PropertyChanged(this, new PropertyChangedEventArgs("Items"));
      153. }
      154. }
      155. }
      156. /// <summary>
      157. /// Erstellt einen neuen Pfad.
      158. /// </summary>
      159. public TrackerPath()
      160. {
      161. values = new Queue<int>();
      162. maxStored = 100;
      163. maximum = 100;
      164. lineWidth = 1;
      165. isFilled = false;
      166. }
      167. private void CutQueue()
      168. {
      169. while (values.Count > maxStored)
      170. values.Dequeue();
      171. }
      172. /// <summary>
      173. /// Fügt diesem Pfad einen neuen Wert hinzu.
      174. /// </summary>
      175. /// <param name="value"></param>
      176. public void Add(int value)
      177. {
      178. values.Enqueue(value);
      179. if (maxStored < values.Count)
      180. CutQueue();
      181. if (PropertyChanged != null)
      182. PropertyChanged(this, new PropertyChangedEventArgs("Items"));
      183. }
      184. public IEnumerator<int> GetEnumerator()
      185. {
      186. return values.GetEnumerator();
      187. }
      188. IEnumerator IEnumerable.GetEnumerator()
      189. {
      190. return values.GetEnumerator();
      191. }
      192. public void CopyTo(int[] array, int index)
      193. {
      194. values.CopyTo(array, index);
      195. }
      196. public void CopyTo(Array array, int index)
      197. {
      198. values.CopyTo((int[])array, index);
      199. }
      200. public int Count
      201. {
      202. get { return values.Count; }
      203. }
      204. public bool IsSynchronized
      205. {
      206. get { return false; }
      207. }
      208. public object SyncRoot
      209. {
      210. get { return (values as ICollection).SyncRoot; }
      211. }
      212. }
      213. }

      Aufgrund des oben genannten Systems, immer nur hinten einen Wert anzufügen, habe ich als internen Speicher eine Queue (also eine first-in-first-out List) gewählt. Dies macht mir weitere Implementierungen leichter. In der CutQueue-methode wird solange das erste Element aus der Queue entfernt, bis die Größe wieder stimmt. Diese Methode wird immer aufgerufen, wenn die Queue-Größe die gewünschte Maximalkapazität überschreitet.
      Die Properties lösen alle das PropertyChanged-Event aus und führen dabei Überprüfungen durch, um unnötige Aufrufe zu vermeiden.
      Alle restlichen Member (die ohne XML-Kommentar) sind lediglich Standardimplementierungen für IEnumerable und ICollection.

      Damit wäre die TrackerPath-Klasse soweit fertig. Auf dieser Basis können wir nun im nächsten Teil weiterarbeiten.

      Erstellen eines einfachen Tracker-Controls - Fortsetzung 2

      Als nächstest brauchen wir eine Liste, mit der das Tracker-Control später die TrackerPath-Instanzen speichern kann. Da würde einem natürlich erst mal die List(Of T) eins Auge springen, doch nach genauerem betrachten ist diese ungeeignet. Warum? Wenn wir einen neuen Path hinzufügen, dann müssen wir in unserem Tracker-Control das PropertyChanged-Event das jeweiligen Paths abonnieren, damit wird darauf reagieren und neuzeichnen können. Genauso müssen wir das Event auch wieder deabonnieren, wenn ein Pfad aus der liste entfernt wird. Da die List(Of T) aber keine Möglichkeit bietet festzustellen, wann ein Item hinzugefügt oder entfernt wurde, müssen wir und mit etwas eigenem aushelfen.
      Wir erstellen uns also eine neue Klasse (in meinem Fall "TrackerPathCollection"), die das kann. Das Klassendiagramm dazu sieht so aus:


      Bei diesem Teil geht ein Danke an @ErfinderDesRades:, der mir geholfen hat den Code stark zu vereinfachen.

      Ich erbe von der Klasse Collection(Of T), da diese alle Funktionalität mitbringt, die wir benötigen. Ich überschreibe lediglich ein paar Methoden, um die jeweiligen Events dort auszulösen, denn das ist ja der Sinn dieser Klasse. Das Ganze sieht so aus:

      VB

      VB.NET-Quellcode

      1. Imports System.Collections.ObjectModel
      2. ''' <summary>
      3. ''' Speichert eine Auflistung von Tracker-Pfaden.
      4. ''' </summary>
      5. Public Class TrackerPathCollection : Inherits Collection(Of TrackerPath)
      6. Public Event ItemAdded As EventHandler(Of TrackerPathCollectionEventArgs)
      7. Public Event ItemRemoving As EventHandler(Of TrackerPathCollectionEventArgs)
      8. Protected Overrides Sub ClearItems()
      9. For Each _item In Me
      10. RaiseEvent ItemRemoving(Me, New TrackerPathCollectionEventArgs(_item))
      11. Next
      12. MyBase.ClearItems()
      13. End Sub
      14. Protected Overrides Sub InsertItem(index As Integer, item As TrackerPath)
      15. MyBase.InsertItem(index, item)
      16. RaiseEvent ItemAdded(Me, New TrackerPathCollectionEventArgs(item))
      17. End Sub
      18. Protected Overrides Sub RemoveItem(index As Integer)
      19. RaiseEvent ItemRemoving(Me, New TrackerPathCollectionEventArgs(Me(index)))
      20. MyBase.RemoveItem(index)
      21. End Sub
      22. Protected Overrides Sub SetItem(index As Integer, item As TrackerPath)
      23. RaiseEvent ItemRemoving(Me, New TrackerPathCollectionEventArgs(Me(index)))
      24. MyBase.SetItem(index, item)
      25. RaiseEvent ItemAdded(Me, New TrackerPathCollectionEventArgs(item))
      26. End Sub
      27. End Class
      28. Public Class TrackerPathCollectionEventArgs : Inherits EventArgs
      29. Private _item As TrackerPath
      30. Public ReadOnly Property Item As TrackerPath
      31. Get
      32. Return _item
      33. End Get
      34. End Property
      35. Public Sub New(item As TrackerPath)
      36. _item = item
      37. End Sub
      38. End Class
      C#

      C-Quellcode

      1. using System;
      2. using System.Collections.Generic;
      3. using System.Collections.ObjectModel;
      4. using System.Linq;
      5. using System.Text;
      6. using System.Threading.Tasks;
      7. namespace TrackerControl
      8. {
      9. /// <summary>
      10. /// Speichert eine Auflistung von Tracker-Pfaden.
      11. /// </summary>
      12. public class TrackerPathCollection : Collection<TrackerPath>
      13. {
      14. public event EventHandler<TrackerPathCollectionEventArgs> ItemAdded;
      15. public event EventHandler<TrackerPathCollectionEventArgs> ItemRemoving;
      16. protected override void ClearItems()
      17. {
      18. if (ItemRemoving != null) foreach (var itm in this) ItemRemoving(this, new TrackerPathCollectionEventArgs(itm));
      19. base.ClearItems();
      20. }
      21. protected override void InsertItem(int index, TrackerPath item)
      22. {
      23. base.InsertItem(index, item);
      24. if (ItemAdded != null) ItemAdded(this, new TrackerPathCollectionEventArgs(item));
      25. }
      26. protected override void RemoveItem(int index)
      27. {
      28. if (ItemRemoving != null) ItemRemoving(this, new TrackerPathCollectionEventArgs(this[index]));
      29. base.RemoveItem(index);
      30. }
      31. protected override void SetItem(int index, TrackerPath item)
      32. {
      33. if (ItemRemoving != null) ItemRemoving(this, new TrackerPathCollectionEventArgs(this[index]));
      34. base.SetItem(index, item);
      35. if (ItemAdded != null) ItemAdded(this, new TrackerPathCollectionEventArgs(item));
      36. }
      37. }
      38. public class TrackerPathCollectionEventArgs : EventArgs
      39. {
      40. TrackerPath item;
      41. public TrackerPath Item
      42. {
      43. get
      44. {
      45. return item;
      46. }
      47. }
      48. public TrackerPathCollectionEventArgs(TrackerPath item)
      49. {
      50. this.item = item;
      51. }
      52. }
      53. }

      An allen Stellen, an denen Items hinzugefügt oder entfernt werden, befindet sich ein Aufruf an ItemAdded oder an ItemRemoving. Für diese beiden Events habe ich auch eine kleine Event-Klasse erstellt, die das betroffene Item (in diesem Fall eben ein TrackerPath) speichern kann. Beachtet auch, dass man an den Namen erkennt, dass ItemAdded ausgelöst wird, nachdem ein Item hinzugefügt wurde, ItemRemoved aber bevor ein Item entfernt wurde.

      Das wäre dann auch schon alles für diese kleine Auflistungs-Klasse, weiter gehts (wegen Platzmangel) im nächsten Teil mit dem eigentlichen Control.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Artentus“ ()

      Erstellen eines einfachen Tracker-Controls - Fortsetzung 3

      Wir haben nun alle nötigen Vorbereitungen abgeschlossen, jetzt können wir uns ans eigentliche Control ranmachen. Dadurch, dass wird alles vorher sauber in Klassen ausgelagert haben, ist das Klassendiagramm dafür auch nicht sehr lang. Wir brauchen eigentlich lediglich die Liste, die die Pfade speichert, und die Anzahl an Werten, die wir anzeigen wollen. Ich habe zusätzlich noch eine Farbe für einen Rahmen und eine Möglichkeit, eine kleine Legende (also Beschriftungen für die Pfade) anzuzeigen, hinzugefügt.

      Dazu wäre noch zu sagen, dass die klasse natürlich von Control erbt, und dass "~Tracker" der Destruktor ist (für diejenigen, die kein C# können, das entspricht in VB der Finalize-Methode).

      Hier wäre erstmal der Code:
      VB

      VB.NET-Quellcode

      1. Imports System.Windows.Forms
      2. Imports System.Drawing
      3. Imports System.Drawing.Drawing2D
      4. Imports System.Drawing.Text
      5. Imports System.ComponentModel
      6. ''' <summary>
      7. ''' Ein Trackercontrol, das mehrere Pfade unterstützt und vielseitig anpassbar ist.
      8. ''' </summary>
      9. <DesignerCategory("Code")>
      10. Public Class Tracker : Inherits Control
      11. Private _paths As TrackerPathCollection
      12. Private pens As Dictionary(Of TrackerPath, Pen)
      13. Private brushes As Dictionary(Of TrackerPath, Brush)
      14. Private nameBrushes As Dictionary(Of TrackerPath, Brush)
      15. Private _showedValueCount As Integer
      16. Private _borderColor As Color
      17. Private borderPen As Pen
      18. Private format As StringFormat
      19. Private _showPathNames As Boolean
      20. ''' <summary>
      21. ''' Erstellt einen neuen Tracker.
      22. ''' </summary>
      23. Public Sub New()
      24. SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.UserPaint Or ControlStyles.ResizeRedraw, True)
      25. UpdateStyles()
      26. _paths = New TrackerPathCollection()
      27. AddHandler Paths.ItemAdded, AddressOf Paths_Added
      28. AddHandler Paths.ItemRemoving, AddressOf Paths_Removing
      29. ShowedValueCount = 100
      30. ShowPathNames = True
      31. pens = New Dictionary(Of TrackerPath, Pen)()
      32. brushes = New Dictionary(Of TrackerPath, Brush)()
      33. nameBrushes = New Dictionary(Of TrackerPath, Brush)()
      34. format = New StringFormat(StringFormat.GenericDefault)
      35. format.LineAlignment = StringAlignment.Center
      36. End Sub
      37. Protected Overrides Sub Finalize()
      38. For Each item In pens
      39. item.Value.Dispose()
      40. Next
      41. pens.Clear()
      42. For Each item In brushes
      43. item.Value.Dispose()
      44. Next
      45. brushes.Clear()
      46. For Each item In nameBrushes
      47. item.Value.Dispose()
      48. Next
      49. nameBrushes.Clear()
      50. borderPen.Dispose()
      51. format.Dispose()
      52. End Sub
      53. Private Sub Paths_Added(sender As Object, e As TrackerPathCollectionEventArgs)
      54. If e.Item IsNot Nothing Then
      55. AddHandler e.Item.PropertyChanged, AddressOf Path_PropertyChanged
      56. pens.Add(e.Item, New Pen(e.Item.LineColor, e.Item.LineWidth))
      57. brushes.Add(e.Item, New SolidBrush(e.Item.FillColor))
      58. nameBrushes.Add(e.Item, New SolidBrush(e.Item.LineColor))
      59. End If
      60. End Sub
      61. Private Sub Paths_Removing(sender As Object, e As TrackerPathCollectionEventArgs)
      62. If e.Item IsNot Nothing Then
      63. RemoveHandler e.Item.PropertyChanged, AddressOf Path_PropertyChanged
      64. pens(e.Item).Dispose()
      65. pens.Remove(e.Item)
      66. brushes(e.Item).Dispose()
      67. brushes.Remove(e.Item)
      68. nameBrushes(e.Item).Dispose()
      69. nameBrushes.Remove(e.Item)
      70. End If
      71. End Sub
      72. Private Sub Path_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
      73. Dim path = DirectCast(sender, TrackerPath)
      74. Select Case e.PropertyName
      75. Case "LineColor"
      76. nameBrushes(path).Dispose()
      77. nameBrushes(path) = New SolidBrush(path.LineColor)
      78. pens(path).Dispose()
      79. pens(path) = New Pen(path.LineColor, path.LineWidth)
      80. Case "LineWidth"
      81. pens(path).Dispose()
      82. pens(path) = New Pen(path.LineColor, path.LineWidth)
      83. Case "FillColor"
      84. brushes(path).Dispose()
      85. brushes(path) = New SolidBrush(path.FillColor)
      86. End Select
      87. Invalidate()
      88. End Sub
      89. ''' <summary>
      90. ''' Alle Pfade, die momentan diesem Tracker zugeordnet sind.
      91. ''' </summary>
      92. <Browsable(True)>
      93. <Description("Alle Pfade, die momentan diesem Tracker zugeordnet sind.")>
      94. <Category("Data")>
      95. Public ReadOnly Property Paths As TrackerPathCollection
      96. Get
      97. Return _paths
      98. End Get
      99. End Property
      100. ''' <summary>
      101. ''' Die von dem Tracker angezeigte Anzahl Werte (pro Pfad).
      102. ''' </summary>
      103. <Browsable(True)>
      104. <DefaultValue(100)>
      105. <Description("Die von dem Tracker angezeigte Anzahl Werte (pro Pfad).")>
      106. <Category("Behavior")>
      107. Public Property ShowedValueCount As Integer
      108. Get
      109. Return _showedValueCount
      110. End Get
      111. Set(value As Integer)
      112. If _showedValueCount <> value Then
      113. _showedValueCount = value
      114. Invalidate()
      115. End If
      116. End Set
      117. End Property
      118. ''' <summary>
      119. ''' Die Rahmenfarbe des Trackers.
      120. ''' </summary>
      121. <Browsable(True)>
      122. <Description("Die Rahmenfarbe des Trackers.")>
      123. <Category("Appearance")>
      124. Public Property BorderColor As Color
      125. Get
      126. Return _borderColor
      127. End Get
      128. Set(value As Color)
      129. If _borderColor <> value Then
      130. _borderColor = value
      131. If borderPen IsNot Nothing Then borderPen.Dispose()
      132. borderPen = New Pen(_borderColor)
      133. Invalidate()
      134. End If
      135. End Set
      136. End Property
      137. ''' <summary>
      138. ''' Gibt an, ob der Tracker Pfadnamen anzeigt.
      139. ''' </summary>
      140. <Browsable(True)>
      141. <DefaultValue(True)>
      142. <Description("Gibt an, ob der Tracker Pfadnamen anzeigt.")>
      143. <Category("Appearance")>
      144. Public Property ShowPathNames As Boolean
      145. Get
      146. Return _showPathNames
      147. End Get
      148. Set(value As Boolean)
      149. If _showPathNames <> value Then
      150. _showPathNames = value
      151. Invalidate()
      152. End If
      153. End Set
      154. End Property
      155. End Class
      C#

      C-Quellcode

      1. using System;
      2. using System.Collections.Generic;
      3. using System.Linq;
      4. using System.Text;
      5. using System.Threading.Tasks;
      6. using System.Windows.Forms;
      7. using System.Drawing;
      8. using System.Drawing.Drawing2D;
      9. using System.Drawing.Text;
      10. using System.ComponentModel;
      11. namespace TrackerControl
      12. {
      13. /// <summary>
      14. /// Ein Trackercontrol, das mehrere Pfade unterstützt und vielseitig anpassbar ist.
      15. /// </summary>
      16. [DesignerCategory("Code")]
      17. public class Tracker : Control
      18. {
      19. TrackerPathCollection paths;
      20. Dictionary<TrackerPath, Pen> pens;
      21. Dictionary<TrackerPath, Brush> brushes;
      22. Dictionary<TrackerPath, Brush> nameBrushes;
      23. int showedValueCount;
      24. Color borderColor;
      25. Pen borderPen;
      26. StringFormat format;
      27. bool showPathNames;
      28. /// <summary>
      29. /// Erstellt einen neuen Tracker.
      30. /// </summary>
      31. public Tracker()
      32. {
      33. SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true);
      34. UpdateStyles();
      35. paths = new TrackerPathCollection();
      36. paths.ItemAdded += Paths_Added;
      37. paths.ItemRemoving += Paths_Removing;
      38. showedValueCount = 100;
      39. showPathNames = true;
      40. pens = new Dictionary<TrackerPath, Pen>();
      41. brushes = new Dictionary<TrackerPath, Brush>();
      42. nameBrushes = new Dictionary<TrackerPath, Brush>();
      43. format = new StringFormat(StringFormat.GenericDefault);
      44. format.LineAlignment = StringAlignment.Center;
      45. }
      46. ~Tracker()
      47. {
      48. foreach (var item in pens)
      49. item.Value.Dispose();
      50. pens.Clear();
      51. foreach (var item in brushes)
      52. item.Value.Dispose();
      53. brushes.Clear();
      54. foreach (var item in nameBrushes)
      55. item.Value.Dispose();
      56. nameBrushes.Clear();
      57. borderPen.Dispose();
      58. format.Dispose();
      59. }
      60. private void Paths_Added(object sender, TrackerPathCollectionEventArgs e)
      61. {
      62. if (e.Item != null)
      63. {
      64. e.Item.PropertyChanged += Path_PropertyChanged;
      65. pens.Add(e.Item, new Pen(e.Item.LineColor, e.Item.LineWidth));
      66. brushes.Add(e.Item, new SolidBrush(e.Item.FillColor));
      67. nameBrushes.Add(e.Item, new SolidBrush(e.Item.LineColor));
      68. }
      69. }
      70. private void Paths_Removing(object sender, TrackerPathCollectionEventArgs e)
      71. {
      72. if (e.Item != null)
      73. {
      74. e.Item.PropertyChanged -= Path_PropertyChanged;
      75. pens[e.Item].Dispose();
      76. pens.Remove(e.Item);
      77. brushes[e.Item].Dispose();
      78. brushes.Remove(e.Item);
      79. nameBrushes[e.Item].Dispose();
      80. nameBrushes.Remove(e.Item);
      81. }
      82. }
      83. private void Path_PropertyChanged(object sender, PropertyChangedEventArgs e)
      84. {
      85. var path = sender as TrackerPath;
      86. switch (e.PropertyName)
      87. {
      88. case "LineColor":
      89. nameBrushes[path].Dispose();
      90. nameBrushes[path] = new SolidBrush(path.LineColor);
      91. pens[path].Dispose();
      92. pens[path] = new Pen(path.LineColor, path.LineWidth);
      93. break;
      94. case "LineWidth":
      95. pens[path].Dispose();
      96. pens[path] = new Pen(path.LineColor, path.LineWidth);
      97. break;
      98. case "FillColor":
      99. brushes[path].Dispose();
      100. brushes[path] = new SolidBrush(path.FillColor);
      101. break;
      102. }
      103. Invalidate();
      104. }
      105. /// <summary>
      106. /// Alle Pfade, die momentan diesem Tracker zugeordnet sind.
      107. /// </summary>
      108. [Browsable(true)]
      109. [Description("Alle Pfade, die momentan diesem Tracker zugeordnet sind.")]
      110. [Category("Data")]
      111. public TrackerPathCollection Paths
      112. {
      113. get
      114. {
      115. return paths;
      116. }
      117. }
      118. /// <summary>
      119. /// Die von dem Tracker angezeigte Anzahl Werte (pro Pfad).
      120. /// </summary>
      121. [Browsable(true)]
      122. [DefaultValue(100)]
      123. [Description("Die von dem Tracker angezeigte Anzahl Werte (pro Pfad).")]
      124. [Category("Behavior")]
      125. public int ShowedValueCount
      126. {
      127. get
      128. {
      129. return showedValueCount;
      130. }
      131. set
      132. {
      133. if (showedValueCount != value)
      134. {
      135. showedValueCount = value;
      136. Invalidate();
      137. }
      138. }
      139. }
      140. /// <summary>
      141. /// Die Rahmenfarbe des Trackers.
      142. /// </summary>
      143. [Browsable(true)]
      144. [Description("Die Rahmenfarbe des Trackers.")]
      145. [Category("Appearance")]
      146. public Color BorderColor
      147. {
      148. get
      149. {
      150. return borderColor;
      151. }
      152. set
      153. {
      154. if (borderColor != value)
      155. {
      156. borderColor = value;
      157. if (borderPen != null)
      158. borderPen.Dispose();
      159. borderPen = new Pen(borderColor);
      160. Invalidate();
      161. }
      162. }
      163. }
      164. /// <summary>
      165. /// Gibt an, ob der Tracker Pfadnamen anzeigt.
      166. /// </summary>
      167. [Browsable(true)]
      168. [DefaultValue(true)]
      169. [Description("Gibt an, ob der Tracker Pfadnamen anzeigt.")]
      170. [Category("Appearance")]
      171. public bool ShowPathNames
      172. {
      173. get
      174. {
      175. return showPathNames;
      176. }
      177. set
      178. {
      179. if (showPathNames != value)
      180. {
      181. showPathNames = value;
      182. Invalidate();
      183. }
      184. }
      185. }
      186. }
      187. }

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

      Erstellen eines einfachen Tracker-Controls - Fortsetzung 4

      (das gehört eigentlich noch in der vorherigen post)
      Den Eigenschaften habe ich diesmal noch ein paar Attribute gegeben, damit sie im Designer besser zu bedienen sind. Zusätzlich wird bei jeder Änderung auch neugezeichnet (durch Invalidate), damit alle Änderungen auch sofort sichtbar werden.
      Wie ihr seht habe ich das ItemAdded und das ItemRemoving-Event der TrackerPathCollection abonniert, dort abonniere bzw- deabonniere ich dann wiederum das PropertyChanged-Event des jeweiligen Items. Ich habe das System auch noch insofern optimiert, dass ich alle zum zeichnen eines pfades benötigten GDI-Objekte in Dictionaries gespeichert werden, als Key dient dabei das Pfad selbst. Diese Objekte werden beim Hinzufügen erstellt und beim Entfernen freigegeben. Im PropertyChanged-Event aller Pfade werden dann zusätzlich zum Neuzeichnen auch noch diese Objekte falls notwendig angepasst. Auf diese Art verhindere ich, dass ich diese Instanzen jedesmal beim Zeichnen neu erstellen und wieder freigeben muss. Im Destruktor bzw. in der Finalize-Methode müssen dann natürlich noch die verbleibenden Objekte freigegeben werden, wir wollen ja keinen Memoryleak produzieren.


      Nun hätten wir alles soweit erledigt, um zum letzten und schwierigsten Teil vorzuschreiten - dem Paint-Event. Hier müssen wir nun alles, was wir vorher als reine Daten behandelt haben, auf den Bildschirm bringen.
      Hier ist die OnPaint-Methode, die im Code aus dem letzten Post noch gefehlt hat:

      VB

      VB.NET-Quellcode

      1. Protected Overrides Sub OnPaint(e As PaintEventArgs)
      2. Dim g = e.Graphics
      3. Dim [step] = CSng(Width / (ShowedValueCount - 1))
      4. g.SmoothingMode = SmoothingMode.AntiAlias
      5. g.PixelOffsetMode = PixelOffsetMode.HighQuality
      6. g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit
      7. For pathIndex = 0 To Paths.Count - 1
      8. Dim path = Paths(pathIndex)
      9. Dim points = New PointF(ShowedValueCount - 1) {}
      10. Using pathEnum = path.GetEnumerator()
      11. If path.Count > _showedValueCount Then
      12. For i = 0 To path.Count - _showedValueCount - 1
      13. pathEnum.MoveNext()
      14. Next
      15. End If
      16. For i = 0 To _showedValueCount - 1
      17. Dim xPos = i * [step]
      18. If i >= ShowedValueCount - path.Count Then
      19. pathEnum.MoveNext()
      20. Dim yPos = Height - CSng(pathEnum.Current / path.Maximum) * Height
      21. points(i) = New PointF(xPos, yPos)
      22. Else
      23. points(i) = New PointF(xPos, Height)
      24. End If
      25. Next
      26. End Using
      27. Dim gp = New GraphicsPath()
      28. gp.StartFigure()
      29. gp.AddLines(points)
      30. gp.AddLine(New Point(Width, Height), New Point(0, Height))
      31. gp.CloseFigure()
      32. If path.IsFilled Then g.FillPath(brushes(path), gp)
      33. g.DrawPath(pens(path), gp)
      34. gp.Dispose()
      35. Next
      36. g.SmoothingMode = SmoothingMode.HighSpeed
      37. g.PixelOffsetMode = PixelOffsetMode.HighSpeed
      38. Const borderWidth = 10
      39. Const itemHeight = 18
      40. If ShowPathNames Then
      41. Dim wrapPos = Math.Max(((Height \ 2) - borderWidth) \ itemHeight, 1)
      42. Dim itemLeft = borderWidth
      43. Dim biggestItemWidth = 0
      44. For i = 0 To Paths.Count - 1
      45. If (i Mod wrapPos) = 0 Then itemLeft = biggestItemWidth + borderWidth
      46. Dim path = Paths(i)
      47. Dim colorRect = New Rectangle(itemLeft, borderWidth + (i Mod wrapPos) * itemHeight, 10, 10)
      48. g.FillRectangle(nameBrushes(path), colorRect)
      49. g.DrawRectangle(Drawing.Pens.Black, colorRect)
      50. Dim textRect = New Rectangle(colorRect.X + colorRect.Width + 5, colorRect.Y - 5, Width - 25, itemHeight)
      51. Dim f = New Font(Font.FontFamily, 10, FontStyle.Regular, GraphicsUnit.Pixel)
      52. g.DrawString(path.Name, f, Drawing.Brushes.Gray, textRect, format)
      53. Dim textWidth = g.MeasureString(path.Name, f).Width
      54. If textRect.X + textWidth > biggestItemWidth Then biggestItemWidth = textRect.X + CInt(textWidth)
      55. Next
      56. End If
      57. g.DrawRectangle(borderPen, New Rectangle(0, 0, Width - 1, Height - 1))
      58. End Sub
      C#

      C-Quellcode

      1. protected override void OnPaint(PaintEventArgs e)
      2. {
      3. var g = e.Graphics;
      4. var step = (float)Width / (float)(showedValueCount - 1);
      5. g.SmoothingMode = SmoothingMode.AntiAlias;
      6. g.PixelOffsetMode = PixelOffsetMode.HighQuality;
      7. g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
      8. for (int pathIndex = 0; pathIndex < paths.Count; pathIndex++)
      9. {
      10. var path = paths[pathIndex];
      11. var points = new PointF[showedValueCount];
      12. using (var pathEnum = path.GetEnumerator())
      13. {
      14. if (path.Count > showedValueCount)
      15. for (int i = 0; i < path.Count - showedValueCount; i++)
      16. pathEnum.MoveNext();
      17. for (int i = 0; i < showedValueCount; i++)
      18. {
      19. var xPos = i * step;
      20. if (i >= showedValueCount - path.Count)
      21. {
      22. pathEnum.MoveNext();
      23. var yPos = Height - ((float)pathEnum.Current / (float)path.Maximum) * Height;
      24. points[i] = new PointF(xPos, yPos);
      25. }
      26. else
      27. points[i] = new PointF(xPos, Height);
      28. }
      29. }
      30. var gp = new GraphicsPath();
      31. gp.StartFigure();
      32. gp.AddLines(points);
      33. gp.AddLine(new Point(Width, Height), new Point(0, Height));
      34. gp.CloseFigure();
      35. if (path.IsFilled)
      36. g.FillPath(brushes[path], gp);
      37. g.DrawPath(pens[path], gp);
      38. gp.Dispose();
      39. }
      40. g.SmoothingMode = SmoothingMode.HighSpeed;
      41. g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
      42. const int borderWidth = 10;
      43. const int itemHeight = 18;
      44. if (showPathNames)
      45. {
      46. var wrapPos = Math.Max(((Height / 2) - borderWidth) / itemHeight, 1);
      47. var itemLeft = borderWidth;
      48. var biggestItemWidth = 0;
      49. for (int i = 0; i < paths.Count; i++)
      50. {
      51. if ((i % wrapPos) == 0)
      52. itemLeft = biggestItemWidth + borderWidth;
      53. var path = paths[i];
      54. var colorRect = new Rectangle(itemLeft, borderWidth + (i % wrapPos) * itemHeight, 10, 10);
      55. g.FillRectangle(nameBrushes[path], colorRect);
      56. g.DrawRectangle(Pens.Black, colorRect);
      57. var textRect = new Rectangle(colorRect.X + colorRect.Width + 5, colorRect.Y - 5, Width - 25, itemHeight);
      58. var f = new Font(Font.FontFamily, 10, FontStyle.Regular, GraphicsUnit.Pixel);
      59. g.DrawString(path.Name, f, Brushes.Gray, textRect, format);
      60. var textWidth = g.MeasureString(path.Name, f).Width;
      61. if (textRect.X + textWidth > biggestItemWidth)
      62. biggestItemWidth = textRect.X + (int)textWidth;
      63. }
      64. }
      65. g.DrawRectangle(borderPen, new Rectangle(0, 0, Width - 1, Height - 1));
      66. }

      Das ganze werde ich nun Schritt für Schritt erklären, da es doch ziemlich viel ist.

      Zunächst einmal müssen wir uns klar werden, wie wir unsere Daten (also die Pfade) darstellen wollen. Ich möchte bei diesem Control die letzten Werte aus jedem Pfad in gleichmäßigem Abstand über das Control verteilen und jeweils mut Linien verbinden. Möglicherweise soll der Berech unterhalb des Pfades auch noch gefüllt werden.
      Dann stellen wir uns die Frage, was wir dafür alles für Informationen brauchen und welche Schwierigkeiten wir dabei bekommen könnten. Bei dem Tracker brauchen wir die Abstand zwischen zwei Werten, damit wir sie gleichmäßig verteilen können. Ebenfalls brauchen wir auch alle Informationen aus den Eigenschaften des Tracker-Controls und der einzelnen Pfade. Und dann müssen wir jeden Wert in den Pfaden auf das Bildschirmkoordinatensystem abbilden. Mögliche Probleme sind, dass ein Pfad mehr oder weniger Werte enthallten könnte, als wir anzeigen wollen. Bei ersterem müssen wir die richtigen Werte (also immer die letzten) auswählen, im zweiten Fall müssen wir die fehlenden Werte mit 0 ersetzen. Außerdem müssen wir darauf achten, dass das Koordinatensystem von oben nach unten verläuft, wir wollen es aber genau andersherum darstellen.

      Mit diesen Informationen können wir nun anfangen zu coden. Zuerst berechnen wir die Fehlenden Daten, in diesem Fall eben nur den Abstand zwischen den einzelnen Werten.
      Dann brauchen wir natürlich eine Schleife, um alle Pfade zu zeichnen. Ich habe hier eine Zählschleife (For) verwendet und keine Enumerationsschleife (For Each), da diese ungefähr doppelt so schnell ist und es beim Zeichnen immer auf Geschwindigkeit ankommt.
      Jetzt kommt erstmal wieder Berechnung, denn wir müssen für jeden Wert aus dem aktuellen Pfad den Punkt berechnen, an dem er nachher angezeigt wird. Dafür lege ich zuerst ein Array an, dass die Punkte aufnehmen kann. Ich verwende hier absichtlich ein Array und keine List, da wir ohnehin wissen, wie viele Punkte wir brauchen, und es schneller ist. Um an die Werte ranzukommen verwende ich den Enumerator manuell, um ein unnötiges Kopieren zu vermeiden, was noch einmal Leistung verbrauchen würde.
      Nun müssen wir unsere Sonderfälle behandeln. Befinden sich in dem Pfad mehr Werte, als wir anzeigen wollen, dann müssen wir den Enumerator so lange hochzählen, bis wir an dem ersten für uns relevanten Element angekommen sind. Danach können wir in einer Schleife alle Punkte berechnen. Dort gibt es wieder zwei mögliche Fälle. Entweder, an der aktuellen Stelle gibt es einen Wert, dann berechnen wir die Y-Koordnate mittels des Dreisatzes und drehen sie dann um (da das Koordinatensystem ja falschrum ist). Oder es befindet sich kein Wert an der Stelle, dann hemen wir einfach vür die Y-Koordinate 0 an (oder besser die maximale Höhe, denn unser Koordinatensystem ist ja, wie gesagt, gespiegelt). Die X-Koordinate ist in beiden Fällen gleich und kann ganz simpel durch den Abstand und den Index berechnet werden.
      Somit hätten wir unsere Punkte fertig im Array, jetzt kann das eigentliche zeichnen beginnen. Für mein Vorhaben eignet sich ein GraphicsPath am besten, da ich damit sowohl die Linie alsauch die Füllung zeichnen kann und dieser auch schneller ist, als wenn ich alle Linien einzeln zeichnen würde. Ihr könnt/müsst natürlich für andere Controls auch andere Arten des Zeichnens wählen, GDI+ gibt einem da ja viele Möglichkeiten. Ich jedenfalls füge dem Pfad einfach alle berechneten Punkte hinzu und fertig. Allerdings muss ich die dadurch entstehende Figur unten auch "abschließen", damit die Füllung funktioniert (also ich muss unten bei der X-Achse ebenfalls eine Linie einfügen, da die Figur an der Stelle sonst offen wäre). Diesen pfad kann ich dann ganz einfach zeichnen und bei bedarf auch füllen.

      Damit hätten wir die erste Hälfte der Methode durchgesprochen. Wies mit dem zweiten Teil aussieht, erfahrt ihr dann im nächsten Teil.

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

      Erstellen eines einfachen Tracker-Controls - Fortsetzung 5

      Das wärs mir den Pfaden gewesen, aber wie ich ja oben bereist erwähnt habe möchte ich noch ein paar Gimmicks zu dem Control hinzufügen. Das wäre zum einen ein Rahmen, der aber sehr leicht zu zeichnen ist. Das ist die letzte Codezeile innerhalb der Funktion und sollte soweit selbsterklärend sein. Zum anderen wäre das eine kleine Legende, in der jedem Pfad sein Name zugeordnet wird.
      Wieder stelle ich mir also die Frage, "Wie will ich das anzeigen und was für Probleme kommen dabei auf mich zu?". In meinem Fall möchte ich ein kleines Quadrat in der Farbe des Pfades zeichnen und daneben den Namen das Pfades. Das ganze soll in der linken oberen Ecke angezeigt werden. Eventuelle Probleme könnten sein, dass das Control zu klein ist, um alle Namen untereinander anzeigen zu lassen, weshalb ich dafür sorgen muss, dass die Liste in einem solchen Fall umgebrochen wird (also eine neue Spalte angelegt wird). Außerdem dürfen diese Elemente (also Kästchen und Schrift) nicht zu groß sein, da sie sonst das Control überdecken würden.

      Zuerst hole ich mir also wieder die nötigen Daten. Das wären zwei Konstanten, die ich so gewählt habe, dass es gut aussieht, und die sich nur auf Position und Größe beziehen. Dann brauche ich den Punkt, an dem eine neue Spalte begonnen werden soll. Das habe ich so gestaltet, dass umgebrochen wird, sobald über die Hälfte des Controls erreicht ist. Dann noch zwei Variablen zum zwischenspeichern von Werten und das wars.
      Nun laufe ich wieder mit einer Schleife durch alle Pfade und bestimme, ob bei dem momentanen Index ein Umbruch vorliegt. Wenn ja, dann erhöhe ich die X-Koordinate für die Items entsprechend. Dann wird das Kästchen und der Text gezeichnet, was glaube ich nicht viel Klärungsbedarf hat. Zuletzt muss ich noch messen, wie viel platzt der Text beansprucht, damit ich die nächste Spalte korrekt ausrichten kann.


      So, das wars dann auch schließlich mal mit diesem äußerst langen Artikel. Ich hoffe mir ist die Mischung aus Sourcecode-Präsentation und Tutorial gelungen und ihr konntet etwas über das Erstellen von Controls lernen.
      Also in Zukunft immer daran denken: nicht einfach drauf los programmieren, sondern erstmal ein Grundgerüst erstellen, z.B. mit einem Klassendiagramm.

      Hier habt ihr zum Abschluss noch mal das komplette Klassendiagramm:


      Das ganze Projekt gibt es außerdem in VB und C# im Anhang zu Download, zusammen mit einer kleinen Testanwendung.
      Dateien

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

      Was verwendest du um den Tracker zu demonstrieren? Prozessorauslastung? Wenn ja könntest du das im Testprojekt umstellen? Auf eine Trackbar zum Beispiel denn es crashed mit folgender Fehlermeldung:
      Indikatornamensdaten können nicht geladen werden, da ein ungültiger Index "" aus der Registrierung gelesen wurde.


      Würde es ja selber machen wenn ich eine Ahnung von C# hätte :S ...

      8-) faxe1008 8-)
      Das ist allerdings seltsam, denn eigentlich werden die PerformanceCounter dynamisch je nach Prozessoranzahl erstellt.
      Der Code für den Tracker liegt aber auch in VB vor. Dennoch werd ich da mal schnell was machen.

      So, habs jetzt neu hochgeladen. Diesmal ist auch eine Testanwendung in VB mit drin.
      Als ich die geschrieben habe ist mir übrigens aufgefallen, dass die VB-Version noch einen kleinen Bug hatte. Ich hatte vergessen, dass Arrays in VB immer um 1 größer sind, als in C#, weshalb dann immer ein leerer Punkt am Ende war, was natürlich die Darstellung kaputt gemacht hat. Das ist jetzt behoben.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Artentus“ ()

      Hallo,

      Ich habe jetzt dein Beispielprojekt heruntergeladen und ausprobiert. Wenn ich jetzt aber nochmal einen Tracker auf der Form haben will und diesen von der Toolbox auf die Form ziehe kommt eine Fehlermeldung (Bild im Anhang).

      Gruß
      Switcherlapp97
      Bilder
      • tracker.png

        45,08 kB, 672×323, 412 mal angesehen
      RubiksCubeSolver


      Jetzt im Showroom
      Wieso lässt sich eigentlich kein anderer Pen-Style (DashStyle) auf den Pen anwenden?

      VB.NET-Quellcode

      1. pens(path).DashStyle = DashStyle.Dot
      Mfg: Gather
      Private Nachrichten bezüglich VB-Fragen werden Ignoriert!


      Ich habe ich bisschen Gegoogelt, und so sollte man eine Linie Punktieren können.
      Ja es stimmt sie wirft keinen Fehler, dennoch aber kein ergebnis.
      Mfg: Gather
      Private Nachrichten bezüglich VB-Fragen werden Ignoriert!