Dupe Files - Preview Screenshots 2

Ich mache weiter gute Vorschritte mit der neuen Dupe Files Version.

Was ich neu integriert habe:

  • Objekt-Katalog Verwaltung (neu, öffnen, speichern, bearbeiten)
  • Duplikate nach Gruppe sortieren (Möglichkeiten: Größe, Hash)
  • Vergleich beschleunigt durch Entrümpeln nicht mehr benötigter Einträge der Dateiliste während dem Vergleich
  • Aktionen: Löschen in Papierkorb, Endgültig löschen, durch Link ersetzen (funktioniert noch nicht), durch Textverweis ersetzen (funktioniert noch nicht), in Verzeichnis verschieben, durch 0-Byte Datei ersetzen, komprimieren (funktioniert noch nicht)
  • Dateinamen / Verzeichnisnamen aus Liste entfernen

Was noch zu tun ist:

  • Assistent für Programmstart
  • Assistent für das Bearbeiten der Duplikate (Aktions-Assistent)

Dupe Files - Preview Screenshots

Ich arbeite gerade wieder an einem meiner Lieblings-Projekte "Dupe Files". Ein Projekt, das ich leider vor langer Zeit unvollständig liegen gelassen habe. Die letzte lauffähige Dupe Files Version stammt von 2005 und wurde von mir noch unter Delphi entwickelt.

Die aktuelle Version entwickle ich stattdessen unter Visual Basic .NET, da ich beruflich nun viel mit dieser Sprache zu tun habe. In der ersten Testversion hatte ich leichte Geschwindigkeitsprobleme bei mehreren 10.000 Dateien. Hinzu kommt, dass das Klassendesign nicht besonders gut gewählt war.

Etwa 3 Jahre und wesentlich mehr Erfahrung später läuft nun auch die neue .NET Version so schnell (wenn nicht schneller) wie die alte Delphi Version (hier kann die letzte alte Version heruntergeladen werden).

Hier einige Screenshots. Bis zur fertigen Version dauert es allerdings noch etwas...

Wie man sieht zeigt Dupe Files nun nur noch Duplikat-Dateien an. Alle anderen werden der Übersicht und der Performance wegen gar nicht erst angezeigt. Ich dachte, was soll der Benutzer mit Dateien anfangen, die gar nicht doppelt sind? Also weg damit...

Außerdem experimentiere ich gerade mit ListViewGroups zur gruppierten Darstellung der Einträge. Über die Bezeichnungen (Small, Medium, Large, Huge) bin ich aber noch nicht ganz glücklich.

VB .NET: Advanced logging method

The following function adds aMessage to the applications DefaultLogWriter. The DefaultLogWriter is a nice little feature included in Visual Basic .NET which allows you to automatically create a log file in the "Documents and Settings" folder of your application to which you can add your log entries via WriteLine(aMessage).

The function below also adds some more information to the log entry itself. It adds the current date and time and also retrieves the calling method from stack, which can be useful for tracking down a bug (thanks go out to skybound.ca for his help here).

The output could look like this:

"23.08.2008 16:15:43 : CreateListViewItem : Added new item"

Public Shared Sub AddToLog(ByVal aMessage As String)
 
    If Not My.Settings.Logging Then Exit Sub
 
    Dim nle As String ' new list entry
    Dim acn As String ' A caller name

    ' if logging of stack is enabled put together some info
    ' note: i put this as an option to the application.settings because when adding many entries to the log you might want to speed up things a bit by disabling logging the stack info
    If My.Settings.LogStack Then
 
      Dim sf As System.Diagnostics.StackFrame
      ' the calling method is always frame 1 of the call stack (the current method is frame 0)
      sf = New System.Diagnostics.StackTrace().GetFrame(1)
      acn = sf.GetMethod.ReflectedType.Name & "." & sf.GetMethod.Name
 
    Else
      acn = ""
    End If
 
    ' setup log string
    nle = Now.ToShortDateString & " : " & Now.ToShortTimeString & " : " & acn & " : " & aMessage
 
    ' Add to log
    ' note: you could also add the nle string to a listbox or something else...
    My.Application.Log.DefaultFileLogWriter.WriteLine(nle)
    My.Application.Log.DefaultFileLogWriter.Flush()
  End Sub

VB .NET: Binary compare files

This little function compares two files byte for byte and returns true if they are equal.

Public Shared Function Compare(ByVal aFilename1 As String, ByVal aFilename2 As String) As Boolean
 
      Dim fs1 As New System.IO.FileStream(aFilename1, IO.FileMode.Open, IO.FileAccess.Read)
      Dim fs2 As New System.IO.FileStream(aFilename1, IO.FileMode.Open, IO.FileAccess.Read)
 
      If fs1.Length <> fs2.Length Then
        Return False
      End If
 
      For i As Integer = 0 To fs1.Length - 1 Step i + 1
        If fs1.ReadByte <> fs2.ReadByte Then
          Return False
        End If
      Next
 
      fs1.Close()
      fs2.Close()
 
      Return True
    End Function

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.

VB .NET - Microsoft Word Dokument Variablen auslesen

Um aus einem Word-Dokument die Variablen auszulesen genügt folgender Code und das Einbinden des Verweises "Microsoft Office 9.0" (bzw. höher).

Public Shared Function GetVariables(ByVal aFilename As String) As List(Of String)
 
    Dim result As New List(Of String)
 
    If System.IO.File.Exists(aFilename) Then
 
      Dim wa As New Word.Application
      Dim wd As New Word.Document
 
      Try
        wd = wa.Documents.Open(aFilename)
 
        If wd.Variables.Count > 0 Then
          For Each var As Word.Variable In wd.Variables
            result.Add(var.Name & " = " & var.Value)
          Next
        Else
          result.Add("Keine Variablen in Dokument " & aFilename)
        End If
 
        wd.Close()
        wa.Quit()
 
      Catch ex As Exception
        result.Add(ex.Message)
      End Try
 
    Else
      result.Add("Datei nicht gefunden " & aFilename)
    End If
 
    Return result
  End Function