VB.NET Kalender Steuerelement – Teil 2

Inhaltsverzeichnis
- 1 VB.NET Kalender Steuerelement – Teil 2
- 2 Erweiterungsmethoden erweitert
- 3 Termin-Funktionalitäten – VB.NET Kalender Steuerelement
- 4 IAppointmentService
- 5 AppointmentService
- 6 Termine laden – VB.NET Kalender Steuerelement
- 7 Zwischenstand – VB.NET Kalender Steuerelement
- 8 Termine hinzufügen
- 9 Termine bearbeiten – VB.NET Kalender Steuerelement
- 10 Weitere Klassen
- 11 Termin löschen – VB.NET Kalender Steuerelement
- 12 Kompletter Code – VB.NET Kalender Steuerelement
- 13 Downloads
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 Termin–Referenz.
Ruft intern die DeleteByIdAsync-Methode auf.
AppointmentService
Aufgrund der nicht vorhandenen Datenbank (oder Ähnlichem), fake ich hier praktisch eine Datenquelle mit der Service–Klasse 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?
Einen Unterschied, Welchen wir hier natürlich berücksichtigen müssen, ist z. B. der Datenspeicher, denn während dieses Beispiel zu Demonstrationszwecken nur die Mockup–Daten in der Klasse nutzt, haben wir für gewöhnlich natürlich noch den Netzwerkverkehr und dessen Latenz.
Ladevorgang
QueryParams
Hier finden wir die Filter–Daten 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:

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 Service–Parameter 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.

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 Plus–Knopf 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ösch–Button 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!