VB .NET Programm zum Autostart in der Registrierung hinzufügen

Wer hätte das gedacht... unter .NET ist auch das mittlerweile ein Klacks...

Die folgende Prozedur fügt die aktuelle Application im Bereich CurrentUser in die Registrierung als Autostart Eintrag hinzu.

Der erste Wert "My.Application.Info.Productname" ist dabei der Name des Eintrags und dessen Wert ist der komplette Dateiname des Programms, den man via "System.Reflection.Assembly.GetEntryAssembly.Location" ermitteln kann.

Die Anführungszeichen vorne und hinten sind notwendig, wenn der Pfad zum Programm Leerzeichen enthält. Ansonsten würde Windows nur das Verzeichnis statt das Programm an sich öffnen.

Public Shared Sub AddToAutorun()
    Dim key As Microsoft.Win32.RegistryKey = My.Computer.Registry.CurrentUser.CreateSubKey("Software\Microsoft\Windows\CurrentVersion\Run")
    key.SetValue(My.Application.Info.ProductName, """" & System.Reflection.Assembly.GetEntryAssembly.Location & """")
End Sub

HowTo: VB .NET Enums mit Beschreibungen versehen

Dieses HowTo beschreibt, wie man Enums mit Beschreibungen versehen kann und diese Beschreibungen dann beispielsweise in einer Combobox oder Listbox anzeigt.

Beispiel vorher:

So sehen die Einträge bisher aus. Für den Endanwender ziemlich unpraktisch.

Beispiel nachher:

So sollen die Einträge später angezeigt werden.

Auf gehts!

Unser Beispiel enum:

Public Enum eFileListActionType
  eflatNone = 0
  eflatCompress = 1
  eflatDeleteForever = 2
  eflatRecyclebin = 3
  eflatReplaceWithTextfile = 4
  eflatReplaceWithLink = 5
  eflatReplaceWithZeroByteFile = 6
  eflatMoveToDirectory = 7
End Enum

Um nun alle Namen diesse Enums beispielsweise in einer Combobox anzuzeigen, kann man bekanntermaßen folgendes machen:

Me.cbFileActionType.Items.AddRange([Enum].GetNames(GetType(eFileListActionType)))

Dann hätte man die Combobox mit den Werten eflatNone, eflatCompress usw. usf. gefüllt. Also praktisch so wie im ersten Bild oben. Dass das für den Endanwender nicht unbedingt ideal ist, sollte klar sein. Was wir also benötigen ist eine Möglichkeit den enums eine Beschreibung zu verpassen und diese Beschreibung stattdessen in die Combobox zu schreiben.

Zum Glück gibt es Reflection und das Attribut Description, das uns hierbei aus der Patsche hilft.

Also, wie geht das jetzt?

Zuerst müssen wir unseren enum um das Attribut Description ergänzen. Das sieht dann wie folgt aus:

Imports System.ComponentModel ' Wichtig!

Public Enum eFileListActionType
  <Description("Keine")> _
  eflatNone = 0
  <Description("Komprimieren")> _
  eflatCompress = 1
  <Description("Für immer löschen")> _
  eflatDeleteForever = 2
  <Description("In Papierkorb verschieben")> _
  eflatRecyclebin = 3
  <Description("Durch Textdatei mit Link ersetzen")> _
  eflatReplaceWithTextfile = 4
  <Description("Durch Dateilink ersetzen")> _
  eflatReplaceWithLink = 5
  <Description("Durch 0-Byte Datei ersetzen")> _
  eflatReplaceWithZeroByteFile = 6
  <Description("In Verzeichnis verschieben")> _
  eflatMoveToDirectory = 7
End Enum

Wie man sieht wird einfach der Tag vor einen Eintrag gesetzt.

Zum Auslesen dieser Werte müssen wir nun Reflection bemühen:

Die erste Funktion gibt uns dabei eine Beschreibung für einen Enum-Wert zurück.

Public Shared Function GetEnumDescription(ByVal EnumConstant As [Enum]) As String
    Dim fi As Reflection.FieldInfo = EnumConstant.GetType().GetField(EnumConstant.ToString())
    Dim attr() As System.ComponentModel.DescriptionAttribute = DirectCast(fi.GetCustomAttributes(GetType(System.ComponentModel.DescriptionAttribute), False), System.ComponentModel.DescriptionAttribute())
    If attr.Length > 0 Then
      Return attr(0).Description
    Else
      Return EnumConstant.ToString()
    End If
  End Function

Für folgenden Befehl

Dim str as String = GetEnumDescription(eflatReplaceWithTextfile)
Debug.Print(str)

bekämen wir also das als Ausgabe:

Durch Textdatei mit Link ersetzen

Um das ganze noch etwas zu optimieren habe ich noch eine Hilfs-Funktion geschrieben, die alle Werte eines Enums übersetzt (also die Description einliest) und diese in ein String Array schreibt, das man dann einfach an die Combobox übergeben kann.

Public Shared Function GetEnumDescriptions(ByVal EnumConstant As Type) As String()
    Dim res(-1) As String
    For Each ec As [Enum] In [Enum].GetValues(EnumConstant)
      ReDim Preserve res(UBound(res) + 1)
      res(UBound(res)) = GetEnumDescription(ec)
    Next
    Return res
  End Function

Beispielanwendung:

Me.cbFileActionType.Items.AddRange(GetEnumDescriptions(GetType(eFileListActionType)))

Und damit hätten wir alle Beschreibungen eines Enums sauber in einer Combobox oder Listbox untegebracht.

VB .NET Felder / Properties einer Klasse dynamisch mit einem SQLDataReader befuellen

Manche kennen vielleicht das Problem, auf das man beim Befüllen von Feldern einer bestimmten Klasse durch einen SQLDataReader stößt. Und zwar muss man jedes mal, wenn man die Klasse um ein Feld oder eine Property erweitert, an der entsprechenden Stelle im Code auch dessen Wert zuweisen (zumindest, wenn man sich nicht vom Visual Studio Assistenten eine Klasse bzw. ein Dataset erstellen lässt).

Bisher habe ich sowas für kleinere Programme in etwa wie folgt gemacht:

Public Class Mitarbeiter
  ' Hier eine Beispiel-Klasse für einen Mitarbeiter, 
  ' den wir aus der Datenbank auslesen möchten.
  Public VORNAME As String
  Public NACHNAME As String
  Public ANREDE As String
  Public TELEFON As String
  Public TELEFAX As String
  Public MAIL As String
  Public ART As String
  Public STATUS As String
End Class
Public Class MitarbeiterListe
  ' Dies ist die Liste aller Mitarbeiter aus der Datenbank, 
  ' die wir einlesen wollen.
  ' Beim Erstellen einer neuen Instanz der Klasse MitarbeiterListe, 
  ' wird die Datenbank geöffnet und für jede
  ' zurückgelieferte Zeile eine neue Instanz der Klasse Mitarbeiter 
  ' erstellt und ihr die Werte der verschiedenen Spalten zugewiesen.
  Inherits List(Of Mitarbeiter)
 
  Public Sub New()
    ' Als erstes versuchen wir die Verbindung zur Datenbank zu öffnen
    Try
 
      Using con As New SqlClient.SqlConnection(My.Settings.SQLConnectionString)
 
        Try
          con.Open()
        Catch ex As Exception
          MsgBox(ex.Message, MsgBoxStyle.Critical)
          Exit Sub
        End Try
 
        ' Dann erstellen wir den Command zur Auflistung unserer Mitarbeiter-Query
        Dim cmd As New SqlClient.SqlCommand("Select * From viewMitarbeiter", con)
 
        ' und führen die Abfrage aus
        Using dr As SqlClient.SqlDataReader = cmd.ExecuteReader
 
          ' Nun gehen durch alle zurückgelieferten Datensätze
          Do While dr.Read
 
            ' Neuen Mitarbeiter erstellen
            Dim itm As New Mitarbeiter
 
            ' Werte einzeln den jeweiligen Fields zuweisen
            with itm
              .Vorname = dr.Item("VORNAME")
              .Nachname = dr.Item("NACHNAME")
              .Telefon = dr.Item("TELEFAX")
              .Telefax = dr.Item("TELEFAX")
              ' usw. usf.
            end with
 
            ' Und zur Liste hinzufügen
            Me.Add(itm)
 
          Loop
 
        End Using
 
      End Using
 
    Catch ex As Exception
      MsgBox(ex.Message, MsgBoxStyle.Critical)
      Exit Sub
    End Try
  End Sub
 
End Class

Mittlerweile mache ich das wie folgt:

Public Class MitarbeiterListe
 
  Inherits List(Of Mitarbeiter)
 
  Public Sub New()
 
    ' Als erstes versuchen wir die Verbindung zur Datenbank zu öffnen
    Try
 
      Using con As New SqlClient.SqlConnection(My.Settings.SQLConnectionString)
 
        Try
          con.Open()
        Catch ex As Exception
          MsgBox(ex.Message, MsgBoxStyle.Critical)
          Exit Sub
        End Try
 
        ' Dann erstellen wir den Command zur Auflistung unserer Mitarbeiter-Query
        Dim cmd As New SqlClient.SqlCommand("Select * From viewMitarbeiter", con)
 
        ' und führen die Abfrage aus
        Using dr As SqlClient.SqlDataReader = cmd.ExecuteReader
 
          ' Nun gehen durch alle zurückgelieferten Datensätze
          Do While dr.Read
 
            ' Neuen Mitarbeiter erstellen
            Dim itm As New Mitarbeiter
 
            ' Werte dynamisch den Fields zuweisen
            DataFiller.SetFieldsFromDataReader(itm, dr)
 
            ' Und zur Liste hinzufügen
            Me.Add(itm)
 
          Loop
 
        End Using
 
      End Using
 
    Catch ex As Exception
      MsgBox(ex.Message, MsgBoxStyle.Critical)
      Exit Sub
    End Try
 
  End Sub
 
End Class

Und hier nun meine neue Klasse, die aus der Klasse aObj sämtliche Felder liest und ihr die Werte dynamisch aus dem übergebenen SQLDataReader zuweist.

Public Class DataFiller
 
  Public Shared Sub SetFieldsFromDataReader(ByVal aObj As Object, ByVal aDataReader As SqlClient.SqlDataReader)
    Dim fis As System.Reflection.FieldInfo() = aObj.GetType().GetFields()
 
    ' For each column in the datareader
    For i As Integer = 0 To aDataReader.FieldCount - 1
 
      ' Only assign if we have a value
      If Not aDataReader.IsDBNull(i) Then
 
        ' Check for Field-Name match for each field in our aObj Class
        For Each fi As System.Reflection.FieldInfo In fis
 
          If fi.Name = aDataReader.GetName(i) Then
 
            Try
              fi.SetValue(aObj, aDataReader.Item(i), Reflection.BindingFlags.SetField, Nothing, Nothing)
              Exit For
            Catch ex As Exception
              MsgBox(ex.Message, MsgBoxStyle.Critical)
            End Try
 
          End If
        Next
      End If
    Next
  End Sub
 
End Class

Was meine obige Methode SetFieldsFromDataReader beispielsweise noch nicht kann, ist mit Properties zu arbeiten. Sie arbeitet lediglich mit Feldern. Auch muss man dringend noch die ein oder andere Fehlerbehandlung integrieren. Und optimieren lässt sich auch noch einiges beim Durchlaufen der Schleife. Beispielsweise könnte man vor dem Aufruf von SetFieldsFromDataReader alle Fields der jeweiligen Klasse auslesen und mit übergeben. So würden dann nicht jedes Mal beim Aufruf der Methode immer wieder die Fields ermittelt...

Sollte jemand eine einfachere, oder bessere Lösung haben, würde ich mich sehr freuen, darüber zu hören.

Update am 01.12.2009:

Da ich immer häufiger auf die obigen Funktionen zugreife, habe ich meine Klasse DataFiller mittlerweile etwas ergänzt. Wer möchte, kann sich den Quellcode hier herunter laden.

VB .NET Properties / Felder einer Klasse mit Reflection ermitteln

Mit den folgenden Beispiel Methoden kann man von einer Instanz einer Klasse sämtliche Properties bzw. Felder mit Hilfe von System.Reflection auflisten lassen.

Public Shared Sub GetProperties(ByVal aObj As Object)
    Dim pis As System.Reflection.PropertyInfo() = aObj.GetType().GetProperties()
    For Each pi As System.Reflection.PropertyInfo In pis
      MsgBox(pi.Name)
    Next
  End Sub
 
  Public Shared Sub GetFields(ByVal aObj As Object)
    Dim fis As System.Reflection.FieldInfo() = aObj.GetType().GetFields()
    For Each fi As System.Reflection.FieldInfo In fis
      MsgBox(fi.Name)
    Next
  End Sub

Beispiel:

Public Class person
  public Name as String
  public Alter as Integer
End Class
public Sub ShowFields
  dim p as new Person
  GetFields(p)
End Sub

Ich arbeite gerade an einer Klasse, die dynamisch aus einem DataReader die Properties einer Klasse befüllt. Sobald das fertig ist, gibt es ein etwas komplexeres Beispiel für .NET Reflection.

Update am 01.12.2009:

Da ich immer häufiger auf die obigen Funktionen zugreife, habe ich meine Klasse DataFiller mittlerweile etwas ergänzt. Wer möchte, kann sich den Quellcode hier herunter laden.