Kategorie-Archiv: VB.net

VB.net-Related

ListView und der Doppelclick

Es mag trivial erscheinen – wenn man weiß wie es geht. Aber das ist ja bei vielen Dingen so.

Eine ListView in Visual Studio/ .Net zu benutzen ist eine recht alltägliche Sache. Grund genug dass man hier nicht mit unnötigen Wegen an das Ziel kommt, welches man vor Augen hat.
Will man in einem ListView per Doppelklick auf ein Item rausfinden, welches Item man denn da gerade geklickt hat, kann man in vielen Tutorials teils ziemlich komplizierte Lösungen finden. Da gibt es grundsätzlich zwei verschiedene Ansatzpunkte.
Zum einen guckt man welches Item denn gerade unter dem Cursor ist, wenn man Doppelklickt, oder aber man geht alle Items in der ListView durch und fragt nach Selected ab.
Beides sind Wege die nicht nur Code produzieren, sondern vor allem schwer gegen die persönliche Einschätzung stehen “dass das doch irgendwie einfacher gehen muss”.
Tut es auch.

Wir sind in Visual Studio 2008 und haben eine ListView im Formular. Mit Event ListView.DoubleClick erhalten wie den Funktionsrumpf

    Private Sub MyListView_DoubleClick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyListView.DoubleClick
        
    End Sub

um nun das entsprechende Item zu extrahieren reicht der Auftruf

    Private Sub CustomerFilesListView_DoubleClick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CustomerFilesListView.DoubleClick
        Debug.Print(CType(sender, ListView).SelectedItems(0).Text.ToString)
    End Sub

Will man nun beispielsweise noch eine Id zurückliefern, die hinter dem eigentlichen Anzeigetext steht, dann geht das am einfachsten mit einem Tag. Also einfach beim Anlegen der Items den Item.Tag mit der id (oder einem anderen Key) füllen und dann statt “Text” einfach den “Tag” abfragen.

    Private Sub CustomerFilesListView_DoubleClick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CustomerFilesListView.DoubleClick
        Debug.Print(CType(sender, ListView).SelectedItems(0).Tag.ToString)
    End Sub

Et Voila.

Stored Procedures mit Rückgabewert in VB.net

Sicherlich wurde hierrüber viel geschrieben, es gibt ja nun auch viele Wege eine Stopred Procedure in einer VB.net-Programmierung aufzurufen. Ein oft wiederkehrender Fall ist aber bsw. eine Funktion zu haben, die einen Insert auf eine Tabelle macht und die Id des gerade eingefügten Datensatzes zurückgibt.
Das Vorgehen demonstriere ich hier in drei Schritten.
1. Tabelle anlegen
2. Stored Procedure mit Rückgabewert definieren
3. Funktion erstellen, die einen Datensatz einfügt und als Rückgabewert die neu eingefügte Id zurückliefert.

Tabelle anlegen
Wir definieren eine einfache Tabelle mit einem Feld "id", welches Identity ist. Dazu noch ein einfaches Textfeld.

CREATE TABLE [dbo].[tbl_Test] (
	[id] [int] IDENTITY (1, 1) NOT NULL ,
	[TestText] [nvarchar] (50) COLLATE Latin1_General_CS_AS NULL
) ON [PRIMARY]

In dieser Tabelle wird also bei jedem INSERT automatisch eine id hochgezählt und automatisch vergeben.

Stored Procedure mit Rückgabewert erstellen

Nun wollen wir eine Stored Procedure bauen, welche einen Parameter entgegennimmet, der den TestText repräsentiert und einen weiteren Parameter, der uns die neue ID zurückliefert.

CREATE PROCEDURE dbo.usp_Test_Insert
(
	@Id INT OUTPUT,
	@TestText NVARCHAR(50) = ''

	INSERT INTO dbo.tbl_Test
	(
		TestText
	) VALUES (
		@TestText
	)

	SET @Id =  @@IDENTITY
)

Man sieht, das "magische" ist die @@IDENTITY. Hier wird einfach der letzte zugefügte Wert der Identität zurückgegeben. Was dem spitzfindigen Leser direkt auffallen wird, denn damit ist der Wert strenggenommen ja nicht eindeutig, denn wenn zwischen dem INSERT und dem Abruf der @@IDENTITY ein weiterer Datensatz eingefügt wird, wird ein "falscher" Wert zurückgegeben, aber das Szenario lassen wir mal aussen vor.

Ein kleiner Exkurs sei mir gegönnt. Die Benennung erfolgt bei der Stored Procedure mit dem Präfix "usp". Es mag dem ein oder anderen bewußt sein, dass die Built-In-Procedures beim MSSQL-Server mit "sp" als Präfix benannt sind. Und genau das ist der Grund warum ich mit "usp" (User Defined Procedure) präfixe. Denn der SQL Server sucht bei Procedure-Aufrufen immer erst in den System-Prozeduren (also alle mit "sp"). Benennt man die Userdefined Procedures mit einem anderen Präfix hat man den Vorteil dass man diese Suche in den Systemprozeduren umgehen kann, was einen Performance-Vorteil sichert.

Soviel zum Exkurs.

Funktion zur Ausführung der Stored Procedure

Nehmen wir eine Funktion die TestInsert heißt und führen hierin die Execution der Stored Procedure aus, als Rückgabewert der Funktion definieren wir einen Long, der die Id beinhaltet.

    Imports System.Configuration 'wenn man auf die app.config zugreifen will
    Imports System.Data.SqlClient

    Private Function TestInsert(ByVal TestText As String) As Long
        Dim lReturn As Long 'das ist unsere neue Id
        lReturn = 0

        Dim sConnectionString As String
        'In dem Fall kommt der ConnectionString aus der app.config
        sConnectionString = ConfigurationManager.ConnectionStrings("DatabaseConnectionString").ConnectionString

        Using oSqlConnection As SqlConnection = New SqlConnection(sConnectionString)
            oSqlConnection.Open()

            Dim oSqlCommand As SqlCommand
         
            'Hier definieren wir den Output-Parameter
            oSqlCommand = New SqlCommand("dbo.usp_Test_Insert", oSqlConnection)
            oSqlCommand.CommandType = CommandType.StoredProcedure
            Dim oSqlParameter As SqlParameter = oSqlCommand.Parameters.AddWithValue("@Id", 0)
            oSqlParameter.SqlDbType = SqlDbType.Int
            'Da es ein Output-Parameter ist, überschreiben wir den Direction-Default mit InputOutput
            oSqlParameter.Direction = ParameterDirection.InputOutput

            'Dann den Inputparameter mit Wert einfügen
            oSqlCommand.Parameters.AddWithValue("@TestText", TestText)

            'und "Feuer"
            oSqlCommand.ExecuteNonQuery()

            'In der Parameter-Collection können wir nun den Outputparamter auslesen 
            'und anschließend zurückgeben
            lReturn= CType(oSqlCommand.Parameters("@Id").SqlValue.ToString, Long)

        End Using

        Return lReturn
    End Sub

Ich habe bewußt auf Fehlerbehandlungen und Validierungen verzichtet, weil es das Beispiel nur aufblähen würde. Was ich zeigen wollte, wurde damit im Kern gezeigt und ist – da es doch ziemlich Straight-Forward ist – schnell ausbaufähig.

Heute mal ohne Beispiel-Solution :)

Ein weiterer Quick-And-Dirty Ansatz zur Einstellung des Dienstes gegen Windows Search Indexer im Akkubetrieb

Und hier wie versprochen der zweite Teil des Artikels „Quick-and-Dirty-Ansatz gegen Windows Search Indexer im Akkubetrieb“. Nun schauen wir uns noch das kleine Einstellungsprogramm an. Das Programm an sich ist vielleicht gar nicht so wirklich der Rede wert, weil alles was das Programm im Kern macht schon im ersten Teil erwähnt wurde:
– Werte in die Registry schreiben und lesen mit der Methode „GetSettings“.

Dennoch gibt eine kleine Vorstellung dieses Programms. Auch deswegen weil es neben dem Registry-Zugriff noch ein weiteres kleines Feature gibt, was sicherlich gut zu wissen ist. Ich spreche über die Technik, wie man ein Programm in der Tray verschwinden lässt und somit aus der Taskbar rausbekommt. Man sieht es jeden Tag, und eigentlich ist es auch kein Hexenwerk… wie immer mit der Einschränkung „wenn man weiß wie es geht“. Also frisch ans Werk:
Ich habe ein Formular gebaut, welches zwei Controls enthält um die Werte für unseren Anti-Search-Indexer bearbeitbar zu machen. Zum einen eine Checkbox um dem Dienst zu sagen ob er überhaupt losrennen soll oder nicht und zum anderen eine Textbox in der man die Sekunden eintragen kann, die die Länge eines PollIntervalls definiert.
Neben einem Savebutton und einem NotifierIcon (aus der Toolbox) ist das auch alles, was wir brauchen. Wer es ein wenig hübscher mag – so wie ich – wird sich damit natürlich nicht zufrieden geben und das Formular noch ein wenig „pimpen“, aber das ist Kür.
Entscheidend sind nun zwei Funktionalitäten.

Speichern und Laden
Im Form-Load rufe ich die Funktion „LoadInputValues“ auf. Die Funktion holt sich die Werte aus der Registry und initialisiert damit die entsprechenden Controls im Formular. Ein schicker Zweizeiler.

    Public Sub LoadInputValues()
        Me.txtPollInterval.Text = GetSetting(Application.ProductName, "Parameters", "PollInterval", "10")
        Me.cbIsRunning.Checked = GetSetting(Application.ProductName, "Parameters", "IsRunning", True)
    End Sub

Das Speichern ist analog. Hier für gibt es die Funktion „SaveInputValues“ die im Click-Event des Save-Buttons aufgerufen wird. Als Argument gebe ich das aufrufende Formular (Me) mit um prüfen zu können, in welchem State sich das Formular befindet. Denn nur wenn das Fenster „normal“ ist, soll auch gespeichert werden.
Die Weitergabe von „Me“ ist natürlich in dem Beispiel umgehbar. Bei einem Quick-And-Dirty-Attempt darf man das ruhig mal so machen :)

    Public Sub SaveInputValues(ByVal FormInUse As Form)
        If FormInUse.WindowState.ToString = "Normal" Then
            SaveSetting(Application.ProductName, "Parameters", "PollInterval", Me.txtPollInterval.Text.ToString)
            SaveSetting(Application.ProductName, "Parameters", "IsRunning", Me.cbIsRunning.Checked.ToString)
        End If
    End Sub

Formular anzeigen und in Tray minimieren
Hierzu brauchen wir nur zwei Zutaten. Zum einen müssen wir das Resize-Event programmieren und darüber hinaus müssen wir das NotifierIcon irgendwie beleben, sonst kriegen wir das Programm nicht mehr aus der Tray raus.

Zuerst werden wir uns um das Resize kümmern. Hier wird geprüft auf was für einen Resize-Status genau reagiert werden soll. Nur im Fall von „Minimize“ müssen wir was tun, denn in „Normal“ sind wir schon und „Maximize“ wurde bei mir Disabled bzw. per Dialog-Form als Option rausgenommen.
Soll das Fenster also Minimiert werden, verstecken wir das Formular schlicht und einfach mit „Hide“ und definieren, dass auch der Eintrag in der Taskbar nicht angezeigt werden soll. Dafür blenden wir aber das NotifierIcon ein.

    Private Sub fMain_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resize
        'resize
        If Me.WindowState = FormWindowState.Minimized Then
            Me.ShowInTaskbar = False
            Me.Hide()
            Me.NotifierIcon.Visible = True
        End If
    End Sub

Wie es weitergeht ist wohl schon jetzt ersichtlich. Wir müssen auf das NotifierIcon reagieren (Doppelclick) und dann das Formular wieder einblenden und in der Taskbar anzeigen. Das NotifierIcon blenden wir dann wieder aus. Um unser Formular auch wieder korrekt anzuzeigen, legen wir auch den WindowState fest.

    Private Sub NotifierIcon_DoubleClick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles NotifierIcon.DoubleClick
        'DoubleClick
        Me.Show()
        Me.WindowState = FormWindowState.Normal
        Me.ShowInTaskbar = True
        Me.NotifierIcon.Visible = False
    End Sub

Et voila. Schon sind wir durch. Kurz und schmerzlos: bis zum nächsten Eintrag.

Ein Quick-And-Dirty Ansatz gegen Windows Search Indexer im Akkubetrieb

Es gibt Tage da freut es einen mehr als sonst, dass man ein Softwareentwickler ist. Neulich beispielsweise war so ein Tag an denen man eben mal schnell ein Problem lösen kann und sich damit das Leben – zumindest ein wenig – erleichtern kann.
Mein heutiges Problem war folgendes. Ich bin häufig mit meinem Notebook unterwegs und dann auch auf lange Akkulaufzeiten angewiesen. Nun habe ich aber festgestellt, dass es manche Programme rein gar nicht stört wenn man im Akkubetrieb ist und diese fröhlich weiterlaufen ohne eine Chance zu haben, das per Settings zu ändern.
Mein heutiger Kandidat war Microsoft Windows Search. An sich ein gutes Programm was viel (Such-)Arbeit ersparen kann. Aber eben auch ein Programm welches ziemlich regelmäßig die Festplatte indiziert und damit Strom verbraucht. Wenn man unterwegs ist, ist das meines Erachtens völlig unnötig, eine Indizierung kann durchaus auch nur dann stattfinden, wenn man an der Steckdose hängt.
Nun gibt es zugegebenermaßen die Option die Indizierung anzuhalten, was aber den Nachteil hat, dass man das immer wieder machen muss, wenn man die Steckdose verlässt. Vergisst man das mal, läuft die Indizierung wieder. Also musste eine andere Lösung her und mein Quick and Dirty-Attempt führte mich zu folgendem Algorithmus:

1. Prüfe alle x Sekunden ob Notebook im Akkubetrieb ist oder nicht
2. Wenn in Akkubetrieb, dann Stoppe den Windows Search Indexing Dienst
3. Wenn in Netzbetrieb, dann Starte den Windows Search Indexing Dienst, sofern er nicht schon läuft

Da das ganze im Hintergrund laufen soll, sollte das Programm seinerseits ein Dienst werden. Obendrauf sollte noch eine kleine Anwendung für die Tray um die Einstellungen für den Dienst zu verändern.
Der Service selber steuert sich über einen Timer in Form eines Delegates. Das Abfrageintervall (Polling) sowie alle anderen Einstellungen werden in der Registry abgelegt. das geht flott und einfach mit Bordmitteln des .net-Frameworks und bedarf hier nur einem einzelnen Auftruf.

    Protected Overrides Sub OnStart(ByVal args() As String)
        ' Code zum Starten des Dienstes hier einfügen. Diese Methode sollte Vorgänge
        ' ausführen, damit der Dienst gestartet werden kann.
        MainStart()
    End Sub

    Private Sub MainStart()
        SetPollInterval()
        InitTimer()
    End Sub

    Private Sub SetPollInterval()
        'Setze einen Standardwert (Klassen-Konstante)
        _iPollInterval = POLL_INTERVAL_DEFAULT

        Try
            ' hier wird auf die Registry zugegriffen und aus einem 
            ' speziellen Bereich die Einstellungen geholt.
            ' In dem Fall alles durch Klassen-Konstanten definiert.
            _iPollInterval = GetSetting(_SETTINGS_APPLICATION, _SETTINGS_SECTION, "PollInterval", POLL_INTERVAL_DEFAULT.ToString)
        Catch ex As Exception
            ' Im fall von einem Zugriffsfehler, schreibe in EventLog
            WriteEventLog("[WiSIM.SetPollInterval] Could not access Registry Settings. Using default values. " & ex.Message, 3)
            _iPollInterval = POLL_INTERVAL_DEFAULT
        End Try
    End Sub

#Region "Timer"
    Private Sub InitTimer()
        Try
            ' Timer in Form eines Delegates erstellen.
            KeepAliveDelegate = New TimerCallback(AddressOf ControlTimer)
            KeepAliveTimer = New Timer(KeepAliveDelegate, Nothing, New TimeSpan(0), New TimeSpan(0, 0, _iPollInterval))
        Catch ex As Exception
            'couldn't init Delegates
            WriteEventLog("[WiSIM.InitTimer] " & ex.Message, 3)
        End Try
    End Sub

    ' Hier die Methode, die bei Ablauf eines Timerintervalls aufgerufen 
    ' wird. Hier spielt sich also alles ab und ist sozusagen die 
    ' Main()-Methode des Dienstes
    Private Sub ControlTimer(ByVal State As Object)
        Try
            SetPollInterval()

            ' Wir prüfen noch ein wenig ob nicht noch ein früherer
            ' Durchlauf aktiv ist. Eine Karenz von 2 Sekunden sollte
            ' in dem Fall dafür reichen
            If LastPolling.AddSeconds(_iPollInterval - 2) < Now Then
                LastPolling = Now

                _bIsRunning = True

                Try
                    ' Wieder Zugriff auf die Registry. 
                    _bIsRunning = CType(GetSetting(_SETTINGS_APPLICATION, _SETTINGS_SECTION, "IsRunning", True), Boolean)
                Catch ex As Exception
                    WriteEventLog("[WiSIM.ControlTimer] Could not access Registry Settings. Using default values. " & ex.Message, 3)
                    _bIsRunning = True
                End Try

                If _bIsRunning Then
                    WriteEventLog("WiSIM is enabled.", 1)
                    ' Hier der Aufruf um die eigentliche Funktionalität
                    ' zu starten. Also die Prüfung des Energiestatus etc.
                    Process()
                Else
                    WriteEventLog("WiSIM was disabled.", 1)
                End If
            Else
                WriteEventLog("[WiSIM.ControlTimer] Thread tries to run wild. Attempt caught.", 2)
            End If
        Catch ex As Exception
            WriteEventLog("[WiSIM.ControlTimer] " & ex.Message, 3)
        End Try
    End Sub
#End Region

So, nun müssen wir also den Power Status von unserem Computer rausfinden. Dafür bedienen wir uns einem Kernel32.dll-Aufruf

    Private Declare Auto Function GetSystemPowerStatus Lib "kernel32.dll" (ByRef lpSystemPowerStatus As SYSTEM_POWER_STATUS) As Integer

darüber hinaus nehmen wir noch ein paar Definitionen mit dazu, um das ganze ein wenig strukturierter werden zu lassen. Die Sektion erklärt sich wohl von selber:

#Region "Structures and Enumerations"
    Public Structure SYSTEM_POWER_STATUS
        Public ACLineStatus As ACLineStatus
        Public BatteryFlag As BatteryFlag
        Public BatteryLifePercent As Byte
        Public Reserved1 As Byte
        Public BatteryLifeTime As Integer
        Public BatteryFullLifeTime As Integer
    End Structure

    Public Enum BatteryFlag As Byte
        High = 1
        Low = 2
        Critical = 4
        Charging = 8
        NoSystemBattery = 128
        Unknown = 255
    End Enum

    Public Enum ACLineStatus As Byte
        Offline = 0
        Online = 1
        Unknown = 255
    End Enum

    Public Enum ServiceRunCommand As Byte
        StartCommand = 1
        StopCommand = 2
    End Enum
#End Region

Nun haben wir im Prinzip alles was wir brauchen. Mit GetPowerStatus() können wir den Status unserer Energieversorgung abfragen und später komfortabel abfragen.

    Private Sub Process()
        Dim oPowerState As SYSTEM_POWER_STATUS
        oPowerState = New SYSTEM_POWER_STATUS
        oPowerState = GetPowerStatus()

        If IsNothing(oPowerState) Then
            WriteEventLog("[WiSIM.Process] Could not decide wether to Stop or Start Windows Search Service due to unknown AC Status. Step was skipped.", 2)
            Exit Sub
        End If

        sEventLogEntry = ""
        bWriteSuccessEntry = False

        If oPowerState.ACLineStatus = ACLineStatus.Online Then
            'start
            WriteEventLog("[WiSIM.Process] ACLineStatus is ONLINE", 1)
            ControlIndexingService(ServiceRunCommand.StartCommand)
        Else
            'stop
            WriteEventLog("[WiSIM.Process] ACLineStatus is OFFLINE", 1)
            ControlIndexingService(ServiceRunCommand.StopCommand)
        End If

        If bWriteSuccessEntry And Len(sEventLogEntry) > 0 Then
            WriteEventLog(sEventLogEntry, 1)
        End If
    End Sub

Die Funktion “GetPowerStatus” besteht seinerseits nur aus einem Aufruf der oben definierten Kernel-Funktion:

    Public Function GetPowerStatus() As SYSTEM_POWER_STATUS
        Dim oSystemPowerStatus As SYSTEM_POWER_STATUS
        oSystemPowerStatus = New SYSTEM_POWER_STATUS

        Try
            GetSystemPowerStatus(oSystemPowerStatus)
        Catch ex As Exception
            oSystemPowerStatus = Nothing
            WriteEventLog("[WiSIM.GetPowerStatus] " & ex.Message, 3)
        End Try

        Return oSystemPowerStatus
    End Function

Nun müssen wir nur noch abfragen ob der Windows Search Indexer aktiv ist oder nicht und darauf reagieren. Hierzu stellt .net das ServiceController-Objekt (Namespace System.ServiceProcess) zur Verfügung, das alle Funktionen mitbringt, die wir brauchen. Hier fragen wir einfach über den Dienstnamen (der heißt bei Windows Indexing Service „WSearch“) den Index-Service ab. Das Argument „RunCommand“ gibt an, ob der Service den Start, Stop oder den Pause-Befehl erhalten soll. Entsprechend wird darauf reagiert und der Windows-Dienst gesteuert.

    Public Function ControlIndexingService(ByVal RunCommand As ServiceRunCommand) As Boolean
        Dim oServiceController As ServiceController
        Dim bReturn As Boolean
        bReturn = True

        oServiceController = New ServiceController(_INDEXING_SERVICE_NAME, System.Environment.MachineName)
        oServiceController.Refresh()

        bWriteSuccessEntry = False

        Try
            Select Case RunCommand
                Case ServiceRunCommand.StartCommand
                    'is already running ?
                    bWriteSuccessEntry = True
                    sEventLogEntry = "Windows Search is already running."

                    If oServiceController.Status <> ServiceControllerStatus.Running Then
                        oServiceController.Start()
                        sEventLogEntry = "Windows Search is starting."
                    End If
                Case ServiceRunCommand.StopCommand
                    If oServiceController.CanStop Then
                        oServiceController.Stop()
                        bWriteSuccessEntry = True
                        sEventLogEntry = "Windows Search is stopping."
                    End If
            End Select
        Catch ex As Exception
            WriteEventLog("[WiSIM.ControlIndexingService] " & ex.Message, 3)
            bReturn = False
        End Try

        Return bReturn
    End Function

Das ist auch schon alles. Wie gesagt, es ist ein Quick-and-Dirty-Ansatz der ein wenig im Powercoding-verfahren (zu deutsch: alles schnell, schnell) entstanden ist, der aber sein Dienst klagenfrei verrichtet und mich wieder ein wenig fauler gemacht hat...
Wie oben schon erwähnt, habe ich es mir nicht nehmen lassen, diesen Service um ein Settings-Progrämmchen zu erweitern. Das stelle ich dann in den kommenden Tagen vor.

Simple XML (2/2)

Fortsetzung von Teil 1.

5. Speichern. Da wir ohne weiteres Zutun die Daten bereits bearbeiten können (der erste und einzigste vorhandene Datensatz wird bei Start der Applikation bereits angezeigt), fehlt noch eine Methode um die Veränderungen zurück in das XML-File zu speichern. Das geht genauso unkompliziert wie auch die Datenabholung:

_oDataSet.WriteXml(_sPATH_TO_XML_FILE)

Das ist alles. Nun packen wir das Ganze noch in eine eigene private Sub SaveData und platzieren ein Button auf das Formular der die Methode SaveData bei Klick aufruft.

6. Navigieren. Wir haben nun ein gebundenes Formular vorliegen. Was fehlt ist eine Navigation um auch durch die gelesenen Datensätze zu blättern. Hierzu brauchen wir zwei neue Button:
cmdForward, Text: „>“ und cmdBack, Text: „<“. Beide haben ein Klick-Ereignis.
Mit oben schon erwähnten DataBinding können wir nun in einem Kontext arbeiten, der uns die Navigation ebenfalls sehr einfach macht. Im Falle von cmdForward ist das

Me.BindingContext(_oDataTable).Position += 1

um einfach in den vorhandenen Datensätzen eine Position weiter zu springen und analog

Me.BindingContext(_oDataTable).Position -= 1

um eine Position zurückzugehen.

Das Beste ist: Sie brauchen sich keine Sorgen darüber zu machen, ob Sie über das „Ziel hinaus“ hinausschießen, also ob bsw die Position kleiner als 0 oder größer als die Anzahl der Datensätze wird. Das wird alles im BindingContext für Sie erledigt, sodass es tatsächlich bei den beiden Aufrufen bleiben kann.
Um zu dem letzen Datensatz zu springen, können Sie übrigens einfach

Me.BindingContext(_oDataTable).Position = Me.BindingContext(_oDataTable).Count

aufrufen.

7. Neuer Datensatz. Sehen wir uns nun an, wie man einen neuen Datensatz hinzufügen kann. Hierzu benötigen wir wieder einen neuen Button der mit „Add New“ betitelt wird und cmdAddNew benannt wird. Hierfür definieren wir das Klick-Ereignis:

Private Sub cmdAddNew_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdAddNew.Click
    Dim oDataRow As DataRow

    Me.txtInternalNumber.Text = Me.BindingContext(_oDataTable).Count + 1
    Me.txtTitle.Text = ""
    Me.txtInterpret.Text = ""

    oDataRow = _oDataTable.NewRow()
    With oDataRow
        .Item("InternalNumber") = Me.txtInternalNumber.Text
        .Item("Title") = Me.txtTitle.Text
        .Item("Interpret") = Me.txtInterpret.Text
    End With

    _oDataTable.Rows.Add(oDataRow)
    oDataRow = Nothing

    SaveData()

    Me.BindingContext(_oDataTable).Position = Me.BindingContext(_oDataTable).Count
End Sub

Da wir innerhalb unserer Datentabelle eine neue Zeile einfügen wollen, definieren wir uns zunächst eine neue DataRow. Diese Datarow initialisieren wir dann als neue Zeile (NewRow) und binden die einzelnen Controls an die jeweiligen Datenfelder (Item = Control.Text). Im weiteren Verlauf fügen wir die neue Datenzeile der Datentabelle zu (Rows.Add) und speichern die gesamte XML-Datei ab.
Um die Controls für die Aufnahme neuer Daten vorzubereiten, löschen wir vorher noch alle Eingaben in den Controls raus (Control.Text = „“) bzw. setzen die InternalNumber auf einen eindeutigen Wert. Diesen können wir, wie schon gesehen, ganz einfach aus dem BindingContext ermitteln. Da der Count, also die Anzahl der im BindingContext enthaltenen Elemente, gleich der Anzahl der CDs-Elemente in der XML-Datei entspricht, ist mit Sicherheit immer der folgende Ausdruck eindeutig:

Me.BindingContext(_oDataTable).Count + 1

Damit wir im Anschluß an diese „Vorbereitung zur Dateneingabe“ auch den Datensatz direkt bearbeiten können, springen wir noch mit der letzten Zeile der Methode zu dem eben angelegten Datensatz.

Ein weiterer positiver Seiteneffekt ist, dass wir (sofern wir keine Löschoperationen vornehmen oder das Control txtInternalNumber bearbeitbar lassen) dadurch eine durchängige, aufsteigende Nummerierung aller CDs erhalten.

Da wir schon einen Save-Button auf dem Formular realisiert haben, können wir nun die Applikation starten, den Button „Add New“ drücken, neue Daten eingeben und mit „Save“ die Daten speichern. Ein Blick ins XML-File bestätigt das.

8. Filtern. Um unsere eingangs postulierte Funktionspalette zu komplettieren benötigen wir nun noch einen Filter. In unserem einfachen Beispiel für eine kleine Statistik.
Wir wollen uns nun noch anzeigen lassen, wie viele CDs wir besitzen, in denen der Interpret gleich „Counting Crows“ ist.
Hierzu müssen wir nur die Datentabelle nach unseren gewünschten Kriterien selektieren. Also als Beispiel:

_oDataTable.Select(„Interpret = ‚Counting Crows’“)

Als Rückgabewert dieser Funktion erhalten wir ein Array von DataRows, können also mit der Length-Methode direkt auf die Anzahl der gefilterten Zeilen zugreifen.

Ergebnis. XML ist wirklich einfach, jedenfalls in den grundsätzlichen Funktionen kommt man mit wenigen Zeilen aus um ein einfaches Programm mit XML-Unterstützung zu kreieren. Die (manuelle) Datenbindung ist da ebenso einfach und ermöglicht vielerlei schöne Möglichkeiten. Man denke da nur an die Möglichkeit statt Daten an die „Text“-Eigenschaft an andere Eigenschaften zu binden…

Anmerkung. Dieses Beispiel funktioniert übrigens auch im Compact Framework also bsw als Windows Mobile Programm. Hierbei ist lediglich eine kleine Änderung nötig um den Pfad zum Daten-XML-File herauszufinden. Siehe hierzu das Posting “Pfad der aktuellen Anwendung ermitteln”.

Das Beispiel-Projekt für VS2005 könnt ihr hier herunterladen.