From f1e0bf104f2eea36b8248febb505de3e9e022b1d Mon Sep 17 00:00:00 2001
From: mdn <1stdragon@gmail.com>
Date: Mon, 7 Dec 2015 23:20:32 +0100
Subject: [PATCH] Readme eingebaut und Projekt daran ausgerichtet
---
TimeScheduler/Readme.txt | 47 +++++++++++++
TimeScheduler/TimeScheduler.sln | 5 ++
TimeScheduler/TimeScheduler/Diagnose.xaml | 40 +++++++++++
TimeScheduler/TimeScheduler/Diagnose.xaml.cs | 27 ++++++++
TimeScheduler/TimeScheduler/Domain/IDomain.cs | 3 +
.../TimeScheduler/Domain/Impl/FileDomain.cs | 6 ++
.../TimeScheduler/Domain/Impl/TimeItem.cs | 10 +++
TimeScheduler/TimeScheduler/MainWindow.xaml | 20 +++++-
.../TimeScheduler/TimeScheduler.csproj | 8 +++
.../ViewModel/DiagnoseViewModel.cs | 69 +++++++++++++++++++
.../TimeScheduler/ViewModel/MainViewModel.cs | 65 +++++++++++++----
11 files changed, 286 insertions(+), 14 deletions(-)
create mode 100644 TimeScheduler/Readme.txt
create mode 100644 TimeScheduler/TimeScheduler/Diagnose.xaml
create mode 100644 TimeScheduler/TimeScheduler/Diagnose.xaml.cs
create mode 100644 TimeScheduler/TimeScheduler/ViewModel/DiagnoseViewModel.cs
diff --git a/TimeScheduler/Readme.txt b/TimeScheduler/Readme.txt
new file mode 100644
index 0000000..78b4812
--- /dev/null
+++ b/TimeScheduler/Readme.txt
@@ -0,0 +1,47 @@
+Beispielprojekt für die Anwendung von WPF.
+
+Anforderung:
+
+ Mit der Applikation soll die Arbeitszeit automatisiert erfast und permanent gespeichert werden.
+ Dafür wird pro Zeiteinheit ein Element angelegt, das folgenden Eigenschaften besitzt:
+ - Beschreibung des Elementes
+ - Von-Zeitpunkt (1/4h genau)
+ - Bis-Zeitpunkt (1/4h genau)
+ - Kostenstelle
+ - Typ (Normal, Pause, Urlaub, Krank)
+ Die Darstellung des Elementes soll vom Typ abhängig sein, z.B. Hintergrundfarbe ist normal blau und bei Pause gelb.
+ Es sollen nicht immer alle Elemente dargestellt werden, sondern nur die das selektieren Tages.
+
+ Die aktuelle Arbeitszeit, soll graphisch markiert sein, und deren Endezeit soll automatisch angepasst werden.
+ Beim Sperren/Ruhezustand soll autmoatisch die aktuelle Arbeitszeit abgeschlossen werden und eine neue gestartet
+ werden, aber nur wenn die aktuelle Arbeitszeit > 15min ist.
+
+ Über ein Menü (z.B. Kontextmenü) erstellt man eine neue aktuelle oder löscht eine vorhandene. Zur Einfachheit
+ darf man die aktuelle Arbeitszeit nicht löschen.
+ Desweitern soll man die selektierte Arbeitszeit mit der nächsten Arbeitszeit verschmelzen können, wobei nur die
+ Endezeit auf das selektierte Objekt übernommen wird.
+ Um einen Überblick über die Daten zu bekommen, soll es eine Diagnoseoberfläche geben, in der man
+
+ Beim Start wird die zuletzt gespeicherten Daten geladen und die letzte Arbeitszeit des aktuellen Tages wird als
+ aktuelle Arbeitszeit hergenommen. Bei Änderungen an den Arbeitszeiten, sollen diese direkt permanent gespeichert
+ werden, spätestens beim beenden der Anwendung.
+ Das speichern sollte sehr robust werden, damit die Datei nicht zerstört werden, wenn während des speichern die
+ Anwendung abstürzt.
+
+ Zur Implementierung soll WPF mit dem MVVM-Pattern benutzt werden
+
+Erweiterte Funktionaltiät:
+ - Bei Sperrungen/Ruhezustände über 8h soll das Element nicht als Arbeitzeit gelten sondern entfernt werden
+ - Es erscheint eine Nachfrage nach einer Entsperrung/Aufwachen (aus Ruhezustand) wie die Zeit seit der
+ letzten Sperrung/Ruhezustand eingetragen werden soll
+ - Kostenstellen-Editor einbauen
+ - Die Kostenstellen in Task aufteilen
+
+
+Beschreibung der Struktur:
+ - Oberflächen sind direkt in der Root
+ - ViewModel: die ViewModels für die Oberflächen
+ - Resource: Resourcen die benötigt werden
+ - Model: Klassen, die die Datenbestände beinhalten
+ - Domain: Schnittstellen und Implementierungen für die Datenbeschaffung
+ - Common: Allgmeine Klassen die in jeder WPF-Anwendung benötigt werden
diff --git a/TimeScheduler/TimeScheduler.sln b/TimeScheduler/TimeScheduler.sln
index 13265ff..8dfb62e 100644
--- a/TimeScheduler/TimeScheduler.sln
+++ b/TimeScheduler/TimeScheduler.sln
@@ -7,6 +7,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimeScheduler", "TimeSchedu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimeScheduler.Test", "TimeScheduler.Test\TimeScheduler.Test.csproj", "{8B47037C-584E-41FB-B8B8-14FAD26D7BCA}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{57AA67D7-E7F9-4D31-BAD8-242100BE21C8}"
+ ProjectSection(SolutionItems) = preProject
+ Readme.txt = Readme.txt
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/TimeScheduler/TimeScheduler/Diagnose.xaml b/TimeScheduler/TimeScheduler/Diagnose.xaml
new file mode 100644
index 0000000..ed71295
--- /dev/null
+++ b/TimeScheduler/TimeScheduler/Diagnose.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TimeScheduler/TimeScheduler/Diagnose.xaml.cs b/TimeScheduler/TimeScheduler/Diagnose.xaml.cs
new file mode 100644
index 0000000..9d35139
--- /dev/null
+++ b/TimeScheduler/TimeScheduler/Diagnose.xaml.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace TimeScheduler
+{
+ ///
+ /// Interaktionslogik für Diagnose.xaml
+ ///
+ public partial class Diagnose : Window
+ {
+ public Diagnose()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/TimeScheduler/TimeScheduler/Domain/IDomain.cs b/TimeScheduler/TimeScheduler/Domain/IDomain.cs
index f986d89..37bc9ec 100644
--- a/TimeScheduler/TimeScheduler/Domain/IDomain.cs
+++ b/TimeScheduler/TimeScheduler/Domain/IDomain.cs
@@ -7,6 +7,9 @@ namespace TimeScheduler.Domain
/// Schnittstelle zum Provider der die Daten ermitteln
public interface IDomain
{
+ /// Ermittelt die Monate, für denen Daten vorliegen
+ /// Liste der Monate, in der Daten vorliegen
+ IEnumerable GetMonths();
/// Ermittelt die Werte für den angegebenen Tag
/// Der Tag für den Werte gesucht werden sollen
/// Die Daten, für den angefragten Tag
diff --git a/TimeScheduler/TimeScheduler/Domain/Impl/FileDomain.cs b/TimeScheduler/TimeScheduler/Domain/Impl/FileDomain.cs
index 9a7740e..34d8470 100644
--- a/TimeScheduler/TimeScheduler/Domain/Impl/FileDomain.cs
+++ b/TimeScheduler/TimeScheduler/Domain/Impl/FileDomain.cs
@@ -81,6 +81,12 @@ namespace TimeScheduler.Domain.Impl
#endregion
#region IDomain
+ IEnumerable IDomain.GetMonths()
+ {
+ return allItems_
+ .GroupBy(x => x.From.Year * 100 + x.From.Month)
+ .Select(x => new DateTime(x.Key / 100, x.Key % 100, 1));
+ }
IEnumerable IDomain.GetItems(DateTime date)
{
return allItems_
diff --git a/TimeScheduler/TimeScheduler/Domain/Impl/TimeItem.cs b/TimeScheduler/TimeScheduler/Domain/Impl/TimeItem.cs
index 17f7385..21a7048 100644
--- a/TimeScheduler/TimeScheduler/Domain/Impl/TimeItem.cs
+++ b/TimeScheduler/TimeScheduler/Domain/Impl/TimeItem.cs
@@ -64,6 +64,10 @@ namespace TimeScheduler.Domain.Impl
}
#endregion
+ #region Weitere Eigenschaften
+ public TimeSpan Duration { get { return till_ - from_; } }
+ #endregion
+
#region ITimeItem (Eigenschaften nicht implizit implementieren, sonst funktioniert das Binden nicht)
private int key_;
///
@@ -81,8 +85,11 @@ namespace TimeScheduler.Domain.Impl
set
{
if (SetField(ref from_, value.Subtract(TimeSpan.FromMinutes(value.Minute % 15))))
+ {
if (From > Till)
Till = From;
+ OnPropertyChanged("Duration");
+ }
}
}
@@ -94,8 +101,11 @@ namespace TimeScheduler.Domain.Impl
set
{
if (SetField(ref till_, value.Subtract(TimeSpan.FromMinutes(value.Minute % 15))))
+ {
if (Till < From)
From = Till;
+ OnPropertyChanged("Duration");
+ }
}
}
diff --git a/TimeScheduler/TimeScheduler/MainWindow.xaml b/TimeScheduler/TimeScheduler/MainWindow.xaml
index b9e89f2..4685c69 100644
--- a/TimeScheduler/TimeScheduler/MainWindow.xaml
+++ b/TimeScheduler/TimeScheduler/MainWindow.xaml
@@ -15,6 +15,13 @@
+
+
+
+
+
+
+
@@ -26,13 +33,24 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TimeScheduler/TimeScheduler/TimeScheduler.csproj b/TimeScheduler/TimeScheduler/TimeScheduler.csproj
index d52f202..a89d84b 100644
--- a/TimeScheduler/TimeScheduler/TimeScheduler.csproj
+++ b/TimeScheduler/TimeScheduler/TimeScheduler.csproj
@@ -59,13 +59,21 @@
+
+ Diagnose.xaml
+
+
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
diff --git a/TimeScheduler/TimeScheduler/ViewModel/DiagnoseViewModel.cs b/TimeScheduler/TimeScheduler/ViewModel/DiagnoseViewModel.cs
new file mode 100644
index 0000000..e163136
--- /dev/null
+++ b/TimeScheduler/TimeScheduler/ViewModel/DiagnoseViewModel.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using TimeScheduler.Common;
+using TimeScheduler.Domain;
+using TimeScheduler.Model;
+
+namespace TimeScheduler.ViewModel
+{
+ /// ViewModel für die Diangoseoberfläche
+ class DiagnoseViewModel : NotifyableObject
+ {
+ #region Konstruktion
+ public DiagnoseViewModel() : this(new Domain.Impl.FileDomain()) { Refresh(); }
+
+ /// Konstruktion
+ public DiagnoseViewModel(IDomain provider)
+ {
+ Provider = provider;
+
+ CreateCommands();
+ }
+
+ /// Datenbeschaffer
+ protected IDomain Provider { get; private set; }
+ #endregion
+
+ #region Eigenschaften
+ private IList months_;
+ /// Auflistung der Monate mit Datenbestände
+ public IList Months { get { return months_; } set { if (SetField(ref months_, value) && months_ != null && months_.Count > 0) SelectedMonth = months_.First(); } }
+
+ private DateTime selectedMonth_;
+ /// Aktuell selektierter Monat
+ public DateTime SelectedMonth { get { return selectedMonth_; } set { if (SetField(ref selectedMonth_, value)) Refresh(selectedMonth_); } }
+
+ private IList items_;
+ public IList Items { get { return items_; } set { SetField(ref items_, value); } }
+ #endregion
+
+ #region Kommandos
+ private void CreateCommands()
+ {
+
+ }
+
+ private void Refresh() { Months = Provider.GetMonths().ToList(); }
+
+ private void Refresh(DateTime month)
+ {
+ // Start und Ende festlegen
+ var start = month;
+ var end = month.AddMonths(1);
+
+ // Schleife und zwischenspeicherung aufbauen
+ var items = new List();
+ while (start < end)
+ {
+ // Einzelne Tage abrufen und speichern
+ items.AddRange(Provider.GetItems(start));
+ start = start.AddDays(1);
+ }
+
+ // Und der Oberfläche bekannt geben
+ Items = items;
+ }
+ #endregion
+ }
+}
diff --git a/TimeScheduler/TimeScheduler/ViewModel/MainViewModel.cs b/TimeScheduler/TimeScheduler/ViewModel/MainViewModel.cs
index c8b6f20..9bdad64 100644
--- a/TimeScheduler/TimeScheduler/ViewModel/MainViewModel.cs
+++ b/TimeScheduler/TimeScheduler/ViewModel/MainViewModel.cs
@@ -12,8 +12,6 @@ namespace TimeScheduler.ViewModel
class MainViewModel : NotifyableObject
{
#region Konstruktion
- /// aktuelles Ele
- private ITimeItem lastTimeItem_;
/// Zyklische Aktuallisierung
private System.Timers.Timer timer_;
/// Funktion zum ermitteln der aktuellen Uhrzeit
@@ -85,7 +83,6 @@ namespace TimeScheduler.ViewModel
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { UpdateLastItem(); }
-
/// Datenbeschaffer
protected IDomain Provider { get; private set; }
#endregion
@@ -118,6 +115,19 @@ namespace TimeScheduler.ViewModel
private ITimeItem selectedTimeItem_;
/// Das aktuell selektierte Element, von dem weitere Details angezeigt werden sollen
public ITimeItem SelectedTimeItem { get { return selectedTimeItem_; } set { SetField(ref selectedTimeItem_, value); } }
+
+ private ITimeItem currentTimeItem_;
+ /// Aktuelle Arbeitszeit
+ public ITimeItem CurrentTimeItem
+ {
+ get { return currentTimeItem_; }
+ set
+ {
+ currentTimeItem_.IsCurrentItem = false;
+ currentTimeItem_ = value;
+ currentTimeItem_.IsCurrentItem = true;
+ }
+ }
#endregion
#region Kommandos
@@ -126,7 +136,12 @@ namespace TimeScheduler.ViewModel
RefreshCommand = new RelayCommand(Refresh);
AddNewCommand = new RelayCommand(AddNew);
DeleteCommand = new RelayCommand(Delete, CanDelete);
+ MergeCommand = new RelayCommand(Merge, CanMerge);
+ DiagnoseCommand = new RelayCommand(Diagnose);
SaveCommand = new RelayCommand(Save);
+
+ NextDateCommand = new RelayCommand(NextDate);
+ PreviousDateCommand = new RelayCommand(PreviousDate);
}
public ICommand RefreshCommand { get; private set; }
@@ -152,8 +167,8 @@ namespace TimeScheduler.ViewModel
TimeItems.Add(lastItem);
}
// und abspeichern
- lastTimeItem_ = lastItem;
- lastTimeItem_.IsCurrentItem = true;
+ currentTimeItem_ = lastItem;
+ currentTimeItem_.IsCurrentItem = true;
// Und direkt den aktuellen Wert speichern
UpdateLastItem();
}
@@ -196,10 +211,36 @@ namespace TimeScheduler.ViewModel
public ICommand DeleteCommand { get; private set; }
private void Delete() { TimeItems.Remove(SelectedTimeItem); }
- public bool CanDelete() { return SelectedTimeItem != null; }
+ public bool CanDelete() { return SelectedTimeItem != null && !SelectedTimeItem.IsCurrentItem; }
+
+ public ICommand MergeCommand { get; private set; }
+ private void Merge()
+ {
+ var elem = TimeItems.Where(x => x.From > SelectedTimeItem.From).OrderBy(x => x.From).FirstOrDefault();
+ if(elem != null)
+ {
+ // Zeit einverleiben
+ SelectedTimeItem.Till = elem.Till;
+ // Wenn das zu verschmelzende Elemente, die aktuelle Arbeitszeit ist, dann ersetzen
+ if (elem.IsCurrentItem)
+ CurrentTimeItem = SelectedTimeItem;
+ // Und nun noch das zu verschmelzende Element entfernen
+ TimeItems.Remove(elem);
+ }
+ }
+ public bool CanMerge() { return SelectedTimeItem != null; }
+
+ public ICommand DiagnoseCommand { get; private set; }
+ private void Diagnose() { var diag = new Diagnose(); diag.ShowDialog(); }
public ICommand SaveCommand { get; private set; }
private void Save() { UpdateLastItem(); }
+
+ public ICommand NextDateCommand { get; private set; }
+ private void NextDate() { SelectedDate = SelectedDate + TimeSpan.FromDays(1); }
+
+ public ICommand PreviousDateCommand { get; private set; }
+ private void PreviousDate() { SelectedDate = SelectedDate + TimeSpan.FromDays(-1); }
#endregion
#region Hilfsfunktionen
@@ -210,24 +251,22 @@ namespace TimeScheduler.ViewModel
UpdateLastItem();
// Wenn ein letztes Element existiert und dies eine Dauer >15min besitzt, dann erst ein neues erzeugen
- if (lastTimeItem_ != null && (lastTimeItem_.Till - lastTimeItem_.From) > TimeSpan.FromMinutes(15))
+ if (currentTimeItem_ != null && (currentTimeItem_.Till - currentTimeItem_.From) > TimeSpan.FromMinutes(15))
{
// neues Element erzeugen und belegen
var newItem = Provider.NewItem();
- newItem.From = lastTimeItem_.Till;
- newItem.IsCurrentItem = true;
+ newItem.From = currentTimeItem_.Till;
// Aufnehmen und altes LastItem durch neues ersetzen
TimeItems.Add(newItem);
- lastTimeItem_.IsCurrentItem = false;
- lastTimeItem_ = newItem;
+ CurrentTimeItem = newItem;
}
}
/// Aktualisiert das "Letzte Element"
private void UpdateLastItem()
{
- if (lastTimeItem_ != null)
- lastTimeItem_.Till = currentTime_();
+ if (currentTimeItem_ != null)
+ currentTimeItem_.Till = currentTime_();
}
#endregion