VB.NET Kalender Steuerelement – Teil 2

VB.NET Kalender erstellen – Teil 2
VB.NET Kalender Steuerelement – Teil 2

VB.NET Kalender Steuerelement – Teil 2

Lerne in meinem zweiten Teil des VB.NET Kalender Steuerelement Beitrages, wie Du unter Anderem Termine lädst und Diese im Kalender anzeigst.

Ersten Teil verpasst!?

Du bist auf der Suche nach dem ersten Teil – hier wirst Du fündig!

Erweiterungsmethoden erweitert

Weitere Helferlein

Achtung Wortspiel – hier nehmen wir uns kurz die Zeit, unsere Erweiterungsmethoden wie folgt zu erweitern.

Im Laufe des Beispiels habe ich noch für mich z. B. noch die Notwendigkeit gesehen, einfach an den Start, bzw. Ende eines Tages zu kommen.

Da mir bisher nur die Date-Property einer Date-Instanz bekannt, ist aber ich die API bzw. die Erweiterungsmethoden „vollständig“ halten wollte, habe ich folgende beiden Funktionen implementiert:

Public Module modDateExtensions
    ' ... previous..

    <Extension>
    Public Function StartOfDay(dt As Date)
        Return dt.Date
    End Function

    <Extension>
    Public Function EndOfDay(dt As Date)
        Return New Date(dt.Year, dt.Month, dt.Day, 23, 59, 59)
    End Function

End Module

Diese beiden Funktionen geben jeweils den Anfang, bzw. das Ende eines Tages zurück.

Termin-Funktionalitäten – VB.NET Kalender Steuerelement

Wir müssen uns als nächstes darüber Gedanken machen, wie wir das Laden, Speichern, Bearbeiten, etc. von Terminen abbilden möchten.

Da Dein persönliches Projekt sich eventuell von meinem und Dies sich wieder von dem Projekt eines anderen Entwicklers unterscheiden wird, müssen wir das Ganze natürlich etwas dynamisch gestalten.

Wir haben gewisse Verhaltensweisen„, Welche sich dennoch vermutlich kreuz und quer über die verschiedene Projekte wiederholen werden.

Daher denke ich, dass wir diese Verhaltensweisen gut in einem Interface zusammenfassen können.

Natürlich könnte man hier auch direkt mit einer IService-Schnittstelle beginnen und weiter abstrahieren, aber wir übertreiben es mal für dieses Beispiel hier zumindest nicht.

IAppointmentService

Da die heutige Welt der Programmierung zumeist asynchron abläuft, gestalten wir die Methoden asynchron.

Wer hat schon Lust, dass alles hängt und flackert, während man mit dem Kalender arbeitet..

Ich denke, dass die Methoden, bzw. Funktionen aufgrund der passenden Bezeichnungen für sich und Ihre Aufgabe selbst sprechen.

Public Interface IAppointmentService

    Function FindByIdAsync(id As Integer) As Task(Of IAppointment)

    Function FindAllByIdAsync(ids As IEnumerable(Of Integer)) As Task(Of IEnumerable(Of IAppointment))

    Function FindAllByAsync(params As Object) As Task(Of IEnumerable(Of IAppointment))

    Function CreateAsync(appointment As IAppointment) As Task

    Function UpdateAsync(appointment As IAppointment) As Task

    Function DeleteByIdAsync(id As Integer) As Task

    Function DeleteAsync(appointment As IAppointment) As Task

End Interface

Methoden

FindByIdAsync(id)

Gibt einen Termin mit der angegebenen Id zurück, falls nicht gefunden, sollte die Implementierung Nothing/null zurückgeben.

FindAllByIdAsync(ids)

Sucht mehrere Termine anhand ihrer Ids aus der Datenquelle und gibt Diese zurück.

Wenn kein einziger Termin gefunden wurde, sollte eine leere Liste zurückkommen.

FindAllByAsync(params)

Der Kalender ruft diese Methode des übergebenen Services auf, um die Termine zu laden, dafür muss man ihm mitteilen, wie die Query-Parameter zu generieren sind.

CreateAsync(appointment)

Erstellt einen Termin in der Datenquelle (persistiert Ihn) und weist dem Termin die jeweilige Id zu.

UpdateAsync(appointment)

Schreibt die überarbeiteten Daten eines Termins anhand seiner Id zurück in die Datenquelle.

DeleteByIdAsync(id)

Löscht einen Termin anhand seiner Id aus der Datenquelle.

DeleteAsync(appointment)

Ermöglicht die Löschung eines Termins anhand der TerminReferenz.

Ruft intern die DeleteByIdAsync-Methode auf.

AppointmentService

Aufgrund der nicht vorhandenen Datenbank (oder Ähnlichem), fake ich hier praktisch eine Datenquelle mit der ServiceKlasse und generiere Testdaten.

Public Class AppointmentService
    Implements IAppointmentService

    Private Property Appointments As List(Of Appointment)

    Sub New()
        Appointments = New List(Of Appointment)
        MockUpData()
    End Sub

    Private Sub MockUpData()
        Appointments.Add(New Appointment() With {.Id = 1, .Subject = "Meeting with Max", .Start = Date.Now.AddDays(-2), .[End] = Date.Now.AddDays(-2).AddHours(2)})
        Appointments.Add(New Appointment() With {.Id = 2, .Subject = "Dog training", .Start = Date.Now.AddHours(1), .[End] = Date.Now.AddHours(2)})
        Appointments.Add(New Appointment() With {.Id = 3, .Subject = "Work due!", .Start = Date.Now.AddDays(3), .[End] = Date.Now.AddDays(3).AddHours(1)})
    End Sub

    Public Function FindByIdAsync(id As Integer) As Task(Of IAppointment) Implements IAppointmentService.FindByIdAsync
        Dim result = Appointments.SingleOrDefault(Function(x) x.Id = id)
        Return Task.FromResult(CType(result, IAppointment))
    End Function

    Public Function FindAllByIdAsync(ids As IEnumerable(Of Integer)) As Task(Of IEnumerable(Of IAppointment)) Implements IAppointmentService.FindAllByIdAsync
        Dim results = Appointments.Where(Function(x) ids.Contains(x.Id))
        Return Task.FromResult(CType(results, IEnumerable(Of IAppointment)))
    End Function

    Public Function FindAllByAsync(params As Object) As Task(Of IEnumerable(Of IAppointment)) Implements IAppointmentService.FindAllByAsync
        Dim query = CType(params, QueryParams)
        Dim results = Appointments.Where(Function(x) x.Start >= query.From AndAlso x.Start <= query.To)
        Return Task.FromResult(CType(results, IEnumerable(Of IAppointment)))
    End Function

    Public Function CreateAsync(appointment As IAppointment) As Task Implements IAppointmentService.CreateAsync
        Dim newId = Appointments.Max(Function(x) x.Id)
        newId += 1
        appointment.Id = newId
        Appointments.Add(appointment)
        Return Task.CompletedTask
    End Function

    Public Function UpdateAsync(appointment As IAppointment) As Task Implements IAppointmentService.UpdateAsync
        Dim old = Appointments.Single(Function(x) x.Id = appointment.Id)
        With appointment
            old.Start = .Start
            old.End = .End
            old.Subject = .Subject
            old.Circle = .Circle
        End With
        Return Task.CompletedTask
    End Function

    Public Function DeleteByIdAsync(id As Integer) As Task Implements IAppointmentService.DeleteByIdAsync
        Dim appointment = Appointments.Single(Function(x) x.Id = id)
        Appointments.Remove(appointment)
        Return Task.CompletedTask
    End Function

    Public Function DeleteAsync(appointment As IAppointment) As Task Implements IAppointmentService.DeleteAsync
        Return DeleteByIdAsync(appointment.Id)
    End Function
End Class

Termine laden – VB.NET Kalender Steuerelement

Wenn nicht jetzt, wann dann?

Nunja, die erste aufkommende Frage wird wohl sein, wann man die Termine laden, bzw. wann man den AppointmentService dazu delegieren sollte.

Hier gibt es bei dem VB.NET Kalender wohl verschiedene Ansatzmöglichkeiten, wie beim Navigieren durch die Knöpfe, beim Starten des Programmes und somit durch das Laden des Kalenders und und und.

Was ist aber, wenn der Lade-Prozess noch im Gange ist und der Nutzer in der Zeit schon 5 weitere Male auf „zum nächsten Monat springen“ gedrückt hat?

Ein Unterschied den wir hier natürlich berücksichtigen müssen ist z. B. der Datenspeicher, denn während dieses Beispiel zu Demonstrationszwecken nur die MockupDaten in der Klasse nutzt, haben wir für gewöhnlich natürlich noch den Netzwerkverkehr und dessen Latenz.

Ladevorgang

QueryParams

Hier finden wir die FilterDaten für den Aufruf an die Such-Funktion des Services

Public Class QueryParams

    Public Property From As Date

    Public Property [To] As Date

    Sub New()
        From = Date.Now.Date.StartOfDay()
        [To] = Date.Now.EndOfDay()
    End Sub

    Sub New(from As Date, [to] As Date)
        Me.From = from
        Me.To = [to]
    End Sub

End Class

QueryParamsEventArgs

Diese Klasse dient der Kapselung von Argumenten an das zugehörige Event.

Public Class QueryParamsEventArgs
    Inherits EventArgs

    Public Property Params As QueryParams

    Sub New()

    End Sub

    Sub New(params As QueryParams)
        Me.Params = params
    End Sub

End Class

Um die Termine dann letztendlich zu laden, erweitern wir unsere „RefreshData„-Methode um folgenden Code:

RefreshData

Private Sub RefreshData()
    ClearEntries()
    RefreshDateLabel()
    RefreshRowHeaders()
    RefreshDays()
    If AppointmentService IsNot Nothing Then
        RefreshAppointments()
    End If
End Sub

RefreshAppointments

Die RefreshAppointments-Methode, Welche ich bewusst async gemacht habe, sieht so aus:

Public Async Sub RefreshAppointments()
    Dim queryParams = New QueryParams([Date].StartOfDay(), [Date].EndOfDay())
    Dim args = New QueryParamsEventArgs(queryParams)
    OnGatheringQueryParams(args)
    Dim results = Await AppointmentService.FindAllByAsync(args.Params)
    DisplayAppointments(results)
End Sub

Protected Sub OnGatheringQueryParams(e As QueryParamsEventArgs)
    RaiseEvent GatheringQueryParams(Me, e)
End Sub

Public Event GatheringQueryParams As EventHandler

Im ersten Schritt erstellen wir neue „QueryParams“ mit den aktuell bekannten Daten des Kalenders, sprich dem ausgewählten Datum.

Danach wird dort drin eine neue Instanz der QueryParamsEventArgs-Klasse erstellt, Welche wir mit den QueryParams befüllen.

Zum Schluss wird die OnGatheringQueryParams()-Methode mit den erstellten QueryParamsEventArgs als Parameter aufgerufen.

Calendar1_GatheringQueryParams

Anschließend könnten wir in der Form1 auf das Ereignis des Kalenders reagieren und die Daten manipulieren, die zur Suche verwendet werden.

    Private Sub Calendar1_GatheringQueryParams(sender As Object, e As QueryParamsEventArgs) Handles Calendar1.GatheringQueryParams
        ' Dim startOfToday = Date.Now.StartOfDay()
        ' Dim endOfToday = Date.Now.EndOfDay()
        ' e.Params.From = startOfToday
        ' e.Params.To = endOfToday
    End Sub

DisplayAppointments

Nachdem die Suche abgeschlossen ist, wird die DisplayAppointments-Methode mit den Ergebnissen aufgerufen und die Termine werden erstmal sortiert.

Nach der Sortierung werden die Termine – unter Berücksichtigung Ihrer Position innerhalb des Tages – in der Oberfläche dargestellt.

    Private Sub DisplayAppointments(appointments As IEnumerable(Of IAppointment))
        appointments = appointments.OrderBy(Function(x) x.Start)
        For Each day In Days
            Dim startOfDaysDate = day.Date.StartOfDay()
            Dim endOfDaysDate = day.Date.EndOfDay()
            Dim appointmentsForThisDay = appointments.Where(Function(x) x.Start >= startOfDaysDate AndAlso x.Start <= endOfDaysDate)
            AddAllDayAppointments(appointmentsForThisDay, day)
        Next
    End Sub

Zwischenstand – VB.NET Kalender Steuerelement

Mit dem aktuellen Zwischenstand können wir Termine wie folgt darstellen:

VB.NET Kalender erstellen Zwischenstand – Termine werden angezeigt
VB.NET Kalender Steuerelement Zwischenstand – Termine werden angezeigt

Nicht vergessen!

Damit das Ganze funktioniert, darfst Du nicht vergessen, dem Kalender einen AppointmentService zu geben, sonst kann er keine Termine laden!

Das kannst Du zum Beispiel durch Dependency-Injection in der Form erledigen, oder manuell zuweisen:

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
    Calendar1.AppointmentService = New AppointmentService()
    Calendar1.RefreshAppointments()
End Sub

Ich habe mich bewusst für diese Lösung entschieden, da ich nicht wollte, dass der Designer rummeckert, weil Ihm der ServiceParameter beim erstellen des Kalenders im Konstruktor fehlt.

Deshalb muss man den Service dann nachher zuweisen..

Termine hinzufügen

Im folgenden Teil zeige ich Dir, wie ich das Hinzufügen von Terminen gedacht und umgesetzt habe.

VB.NET Kalender erstellen - Termin hinzufügen
VB.NET Kalender Steuerelement – Termin hinzufügen

Zwei Möglichkeiten

Ich habe mir dabei zwei Möglichkeiten ausgedacht, in unserem „VB.NET Kalender Steuerelement„-Tutorial Termine hinzuzufügen:

Über Plus-Knopf im Kalender

Die erste Möglichkeit wäre ein allgemeiner PlusKnopf im Kalender, bzw. dessen Verarbeitung in der Form.

Wenn der Plus-Knopf im Kalender gedrückt wird, geben wir ein passendes Event aus und reagieren darauf in der Form.

    ' Calendar Code
    Private Sub btnAddAppointment_Click(sender As Object, e As EventArgs) Handles btnAddAppointment.Click
        OnAddingAppointment(EventArgs.Empty)
    End Sub

    Protected Sub OnAddingAppointment(e As EventArgs)
        RaiseEvent AddingAppointment(Me, e)
    End Sub

    Public Event AddingAppointment As EventHandler

' Form1
    Private Async Sub Calendar1_AddingAppointment(sender As Object, e As EventArgs) Handles Calendar1.AddingAppointment
        Dim appointment = New Appointment()
        With appointment
            .Start = Date.Now
            .End = Date.Now.AddHours(1)
        End With
        Using dlg = New dlgAppointment()
            Dim result = dlg.ShowDialog()
            If result = DialogResult.OK Then
                Await AppointmentService.CreateAsync(appointment)
                ' refresh whole calendar?
                ' refresh only the day?
                ' add the new appointment?
                Calendar1.AddAppointment(appointment)
            End If
        End Using
    End Sub

Doppelklick auf Tag

Die zweite Möglichkeit einen Termin hinzuzufügen wäre, einen Doppelklick auf einen Tag durchzuführen, so könnte man auch direkt den Tag für den Termin vereinfacht wählen.

Bei einem Doppelklick auf einen Tag, verfahren wir ähnlich wie hier drüber:

    ' Calendar Code
    Private Sub Me_DayDoubleClicked(sender As Object, e As DayEventArgs)
        ' or create custom args for passing more info..
        OnDayDoubleClicked(e)
    End Sub

    Protected Sub OnDayDoubleClicked(e As DayEventArgs)
        RaiseEvent DayDoubleClicked(Me, e)
    End Sub

    Public Event DayDoubleClicked As EventHandler(Of DayEventArgs)

    ' Form1 Code
    Private Async Sub Calendar1_DayDoubleClicked(sender As Object, e As DayEventArgs) Handles Calendar1.DayDoubleClicked
        Dim day = e.Day
        Dim appointment = New Appointment()
        With appointment
            .Start = New Date(day.Date.Year, day.Date.Month, day.Date.Day, 8, 0, 0)
            .End = .Start.AddHours(1)
        End With
        Using dlg = New dlgAppointment(appointment)
            Dim result = dlg.ShowDialog()
            If result = DialogResult.OK Then
                Await AppointmentService.CreateAsync(appointment)
                ' refresh whole calendar?
                ' refresh only the day?
                ' add the new appointment?
                Calendar1.AddAppointment(appointment)
            End If
        End Using
    End Sub

Termine bearbeiten – VB.NET Kalender Steuerelement

Im nächsten Schritt verfahren wir analog zum Termin-Hinzufügen wie oben.

Doppelklick auf Termin

Wenn ein Termin doppelt angeklickt wird, löst der Kalender auch hier ein passendes Event aus und wir reagieren in der Form darauf.

    ' Calendar Code
    Private Sub Me_EntryDoubleClicked(sender As Object, e As EntryEventArgs)
        ' or create custom args for passing more info..
        OnEntryDoubleClicked(e)
    End Sub

    Protected Sub OnEntryDoubleClicked(e As EntryEventArgs)
        RaiseEvent EntryDoubleClicked(Me, e)
    End Sub

    Public Event EntryDoubleClicked As EventHandler(Of EntryEventArgs)

    ' Form Code
    Private Async Sub Calendar1_EntryDoubleClicked(sender As Object, e As EntryEventArgs) Handles Calendar1.EntryDoubleClicked
        Dim entry = e.Entry
        Dim copy = CType(entry.Appointment.Clone(), Appointment)
        ' i prefer working with copies first..
        Using dlg = New dlgAppointment(copy)
            Dim result = dlg.ShowDialog()
            If result = DialogResult.OK Then
                Await AppointmentService.UpdateAsync(copy)
                entry.Appointment = copy
            End If
        End Using
    End Sub

Weitere Klassen

Hier sind noch weitere Klassen aus den Beispielen, ich empfehle allerdings dringend, lieber den Code zu laden, da es bei der Menge schwer ist alle Änderungen auch hier im Beitrag zu pflegen.

Public Class DayEventArgs
    Inherits EventArgs

    Public Property Day As Day

    Sub New()
    End Sub

    Sub New(day As Day)
        Me.day = day
    End Sub

End Class

Public Class EntryEventArgs
    Inherits EventArgs

    Public Property Entry As Entry

    Sub New()
    End Sub

    Sub New(entry As Entry)
        Me.Entry = entry
    End Sub

End Class

Termin löschen – VB.NET Kalender Steuerelement

Zu guter Letzt kommen wir zum Punkt, wie man einen Termin löschen kann.

Dafür klickt man einen Termin doppelt an, um Ihn zur Bearbeitung zu öffnen und bekommt dann den LöschButton angezeigt.

Im Code des Termin-Dialoges setzen wir das DialogResult (um nicht vom aktuellen Stand durch Events abzuweichen) einfach auf „Abort„, Welches wir dann in der Form ebenfalls abfragen können.

Ebenso fügen wir dem Kalender eine neue Methode hinzu.

    ' Form Code
    Private Async Sub Calendar1_EntryDoubleClicked(sender As Object, e As EntryEventArgs) Handles Calendar1.EntryDoubleClicked
        Dim entry = e.Entry
        Dim copy = CType(entry.Appointment.Clone(), Appointment)
        ' i prefer working with copies first..
        Using dlg = New dlgAppointment(copy)
            Dim result = dlg.ShowDialog()
            If result = DialogResult.OK Then
                Await AppointmentService.UpdateAsync(copy)
                entry.Appointment = copy
            ElseIf result = DialogResult.Abort Then
                Await AppointmentService.DeleteAsync(entry.Appointment)
                Calendar1.DeleteAppointment(entry.Appointment)
            End If
        End Using
    End Sub

    ' Calendar Code
    Public Sub DeleteAppointment(appointment As IAppointment)
        Dim targetDay = Days.SingleOrDefault(Function(x) appointment.Start >= x.Date.StartOfDay() AndAlso appointment.Start <= x.Date.EndOfDay())
        Dim entry = targetDay.Entries.SingleOrDefault(Function(x) x.Appointment.Id = appointment.Id)
        targetDay.RemoveEntry(entry)
    End Sub

Kompletter Code – VB.NET Kalender Steuerelement

In diesem Beitrag lasse ich die Darstellung des kompletten Code aus, da es so viel ist.

Ich denke es ist für Dich sinnvoller, einfach das Beispielprojekt herunterzuladen!

Downloads

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.