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!