Added cost details window.

This commit is contained in:
Tobias Erbshäußer
2020-06-18 15:58:19 +02:00
parent 06e8fdfc59
commit 6c3fa4bb2e
11 changed files with 824 additions and 43 deletions
+8 -1
View File
@@ -2,6 +2,7 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Windows.Media; using System.Windows.Media;
using UCalc.Controls;
using UCalc.Data; using UCalc.Data;
namespace UCalc namespace UCalc
@@ -11,9 +12,15 @@ namespace UCalc
public static readonly SolidColorBrush MainColor = Brushes.White; public static readonly SolidColorBrush MainColor = Brushes.White;
public static readonly SolidColorBrush SubMainColor = new SolidColorBrush(Color.FromRgb(0x00, 0x7A, 0xCC)); public static readonly SolidColorBrush SubMainColor = new SolidColorBrush(Color.FromRgb(0x00, 0x7A, 0xCC));
public const string DecimalFormat = "0.00"; public const int InternalPrecision = 6;
public const int DisplayPrecision = 2;
public const string DateFormat = "dd.MM.yyyy"; public const string DateFormat = "dd.MM.yyyy";
public static readonly string InternalPrecisionFormat =
Helpers.PrecisionToFormat(InternalPrecision, InternalPrecision - 2);
public static readonly string DisplayPrecisionFormat = Helpers.PrecisionToFormat(DisplayPrecision);
public static readonly ImmutableList<string> SalutationStrs = public static readonly ImmutableList<string> SalutationStrs =
((Salutation[]) Enum.GetValues(typeof(Salutation))).Select(value => value.AsString()).ToImmutableList(); ((Salutation[]) Enum.GetValues(typeof(Salutation))).Select(value => value.AsString()).ToImmutableList();
+5
View File
@@ -37,5 +37,10 @@ namespace UCalc.Controls
{ {
return start1.IsBetween(start2, end2) || start2.IsBetween(start1, end1); return start1.IsBetween(start2, end2) || start2.IsBetween(start1, end1);
} }
public static string PrecisionToFormat(int precision, int optional = 0)
{
return $"0.{new string('0', precision - optional)}{new string('#', optional)}";
}
} }
} }
+207
View File
@@ -0,0 +1,207 @@
<Window x:Class="UCalc.CostEntryDetailsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:UCalc"
xmlns:controls="clr-namespace:UCalc.Controls"
mc:Ignorable="d"
Title="Details und Hilfswerkzeuge"
Width="340"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStyle="ToolWindow"
WindowStartupLocation="CenterOwner"
ShowInTaskbar="False"
Closed="OnClosed">
<StackPanel>
<TabControl x:Name="TabControl">
<TabItem Header="Taschenrechner">
<StackPanel Margin="8"
Height="250">
<TextBox Name="ResultTextBox"
MinHeight="22"
KeyUp="OnResultTextBoxKeyUp" />
<Grid Margin="0, 8, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="CE" Click="OnCEClick" />
<Button Grid.Row="0" Grid.Column="1" Content="(" Click="OnButtonClick" />
<Button Grid.Row="0" Grid.Column="2" Content=")" Click="OnButtonClick" />
<Button Grid.Row="0" Grid.Column="3" Content="/" Click="OnButtonClick" />
<Button Grid.Row="1" Grid.Column="0" Content="1" Click="OnButtonClick" />
<Button Grid.Row="1" Grid.Column="1" Content="2" Click="OnButtonClick" />
<Button Grid.Row="1" Grid.Column="2" Content="3" Click="OnButtonClick" />
<Button Grid.Row="1" Grid.Column="3" Content="*" Click="OnButtonClick" />
<Button Grid.Row="2" Grid.Column="0" Content="4" Click="OnButtonClick" />
<Button Grid.Row="2" Grid.Column="1" Content="5" Click="OnButtonClick" />
<Button Grid.Row="2" Grid.Column="2" Content="6" Click="OnButtonClick" />
<Button Grid.Row="2" Grid.Column="3" Content="-" Click="OnButtonClick" />
<Button Grid.Row="3" Grid.Column="0" Content="7" Click="OnButtonClick" />
<Button Grid.Row="3" Grid.Column="1" Content="8" Click="OnButtonClick" />
<Button Grid.Row="3" Grid.Column="2" Content="9" Click="OnButtonClick" />
<Button Grid.Row="3" Grid.Column="3" Content="+" Click="OnButtonClick" />
<Button Grid.Row="4" Grid.Column="0" Content="Aufrunden" Click="OnRoundClick" />
<Button Grid.Row="4" Grid.Column="1" Content="0" Click="OnButtonClick" />
<Button Grid.Row="4" Grid.Column="2" Content="," Click="OnButtonClick" />
<Button Grid.Row="4" Grid.Column="3" Content="=" Click="OnEvalClick" />
</Grid>
</StackPanel>
</TabItem>
<TabItem Header="Einheitenrechner">
<StackPanel Margin="8"
Height="250">
<TextBlock
TextWrapping="Wrap"
Text="Dieser Rechner kann benutzt werden, um den Verbrauch von einem Preis pro Einheit umzurechnen (z.B. Wasserverbrauch)."
Foreground="{x:Static local:Constants.SubMainColor}" />
<DockPanel Margin="0, 12, 0, 0">
<Label DockPanel.Dock="Left"
Content="Gesamtbetrag:"
Width="160"
Foreground="{x:Static local:Constants.SubMainColor}"
VerticalAlignment="Center" />
<controls:ErrorIcon Margin="12, 0, 0, 0"
DockPanel.Dock="Right"
VerticalAlignment="Center"
Property="{Binding Path=Details.TotalAmount, RelativeSource={RelativeSource AncestorType=local:CostEntryDetailsWindow}}" />
<TextBox
VerticalAlignment="Center"
MinHeight="22"
Text="{Binding Path=Details.TotalAmount.Value, RelativeSource={RelativeSource AncestorType=local:CostEntryDetailsWindow}, UpdateSourceTrigger=PropertyChanged}" />
</DockPanel>
<DockPanel Margin="0, 4, 0, 0">
<Label DockPanel.Dock="Left"
Content="Gesamtverbrauch:"
Width="160"
Foreground="{x:Static local:Constants.SubMainColor}"
VerticalAlignment="Center" />
<controls:ErrorIcon Margin="12, 0, 0, 0"
DockPanel.Dock="Right"
VerticalAlignment="Center"
Property="{Binding Path=Details.UnitCount, RelativeSource={RelativeSource AncestorType=local:CostEntryDetailsWindow}}" />
<TextBox
VerticalAlignment="Center"
MinHeight="22"
Text="{Binding Path=Details.UnitCount.Value, RelativeSource={RelativeSource AncestorType=local:CostEntryDetailsWindow}, UpdateSourceTrigger=PropertyChanged}" />
</DockPanel>
<controls:HighlightButton Margin="0, 8, 0, 0"
HighlightForeground="{x:Static local:Constants.SubMainColor}"
HighlightBackground="{x:Static local:Constants.MainColor}"
Click="OnAddDiscountClick">
<Viewbox Width="16"
Height="16"
Margin="12, 8, 4, 8"
Stretch="Uniform">
<Canvas Width="512" Height="512">
<Canvas.RenderTransform>
<TranslateTransform X="0" Y="0" />
</Canvas.RenderTransform>
<Path>
<Path.Data>
<PathGeometry
Figures="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm144 276c0 6.6-5.4 12-12 12h-92v92c0 6.6-5.4 12-12 12h-56c-6.6 0-12-5.4-12-12v-92h-92c-6.6 0-12-5.4-12-12v-56c0-6.6 5.4-12 12-12h92v-92c0-6.6 5.4-12 12-12h56c6.6 0 12 5.4 12 12v92h92c6.6 0 12 5.4 12 12v56z"
FillRule="NonZero" />
</Path.Data>
</Path>
</Canvas>
</Viewbox>
<Label Content="Abzug hinzufügen"
Margin="0, 8, 12, 8" />
</controls:HighlightButton>
<ScrollViewer HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Visible"
Height="80">
<ItemsControl
ItemsSource="{Binding Path=Details.DiscountsInUnits, RelativeSource={RelativeSource AncestorType=local:CostEntryDetailsWindow}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel Margin="0, 0, 0, 4">
<Label DockPanel.Dock="Left"
Content="Abzug vom Verbrauch:"
Width="160"
Foreground="{x:Static local:Constants.SubMainColor}"
VerticalAlignment="Center" />
<controls:HighlightButton DockPanel.Dock="Right"
HighlightForeground="Red"
HighlightBackground="White"
Click="OnDiscountDeleteClick">
<Viewbox Width="16"
Height="16"
Margin="8, 6, 8, 6"
Stretch="Uniform">
<Canvas Width="448" Height="512">
<Canvas.RenderTransform>
<TranslateTransform X="0" Y="0" />
</Canvas.RenderTransform>
<Path>
<Path.Data>
<PathGeometry
Figures="M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"
FillRule="NonZero" />
</Path.Data>
</Path>
</Canvas>
</Viewbox>
</controls:HighlightButton>
<controls:ErrorIcon Margin="12, 0, 8, 0"
DockPanel.Dock="Right"
VerticalAlignment="Center"
Property="{Binding Path=.}" />
<TextBox
VerticalAlignment="Center"
MinHeight="22"
Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}" />
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</TabItem>
</TabControl>
<DockPanel Margin="8">
<Button x:Name="ApplyButton"
DockPanel.Dock="Right"
Content="Übernehmen"
MinWidth="100"
MinHeight="24"
Click="OnApplyClick"
IsEnabled="False" />
<Label VerticalAlignment="Center"
Content="{Binding Path=ResultStr, RelativeSource={RelativeSource AncestorType=local:CostEntryDetailsWindow}}"
Foreground="{x:Static local:Constants.SubMainColor}" />
</DockPanel>
</StackPanel>
</Window>
+346
View File
@@ -0,0 +1,346 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using UCalc.Annotations;
using UCalc.Controls;
using UCalc.Models;
namespace UCalc
{
public partial class CostEntryDetailsWindow : INotifyPropertyChanged
{
public CostEntryDetailsProperty Details { get; }
private decimal? _result;
public decimal? Result
{
get => _result;
private set
{
if (_result == value)
{
return;
}
_result = value;
ApplyButton.IsEnabled = value != null;
OnPropertyChanged();
OnPropertyChanged("ResultStr");
}
}
public string ResultStr =>
_result != null ? $"Betrag: {_result.Value.ToString(Constants.DisplayPrecisionFormat)} €" : "";
public CostEntryDetailsWindow(CostEntryDetailsProperty details)
{
Details = details;
InitializeComponent();
Details.TotalAmount.PropertyChanged += DetailsPropertyChanged;
Details.UnitCount.PropertyChanged += DetailsPropertyChanged;
Details.DiscountsInUnits.CollectionChanged += DetailsDiscountChanged;
DetailsPropertyChanged(null, null);
if (Details.TotalAmount.ConvertedValue != 0 || Details.UnitCount.ConvertedValue != 0 ||
Details.DiscountsInUnits.Count != 0)
{
TabControl.SelectedIndex = 1;
}
}
private void DetailsPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var totalAmount = Details.TotalAmount.ConvertedValue;
var unitCount = Details.UnitCount.ConvertedValue;
if (totalAmount == null || unitCount == null)
{
Result = null;
return;
}
decimal totalDiscount = 0;
foreach (var discount in Details.DiscountsInUnits)
{
var n = discount.ConvertedValue;
if (n == null)
{
Result = null;
return;
}
totalDiscount += n.Value;
}
Result = unitCount != 0
? totalAmount / unitCount * (unitCount - totalDiscount)
: null;
}
private void DetailsDiscountChanged(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (var discount in Details.DiscountsInUnits)
{
discount.PropertyChanged -= DetailsPropertyChanged;
discount.PropertyChanged += DetailsPropertyChanged;
}
}
private void OnCEClick(object sender, RoutedEventArgs e)
{
ResultTextBox.Text = "";
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
ResultTextBox.Text += (string) ((Button) sender).Content;
}
private void OnRoundClick(object sender, RoutedEventArgs e)
{
ResultTextBox.Text = "ceil(" + ResultTextBox.Text + ")";
}
private void OnEvalClick(object sender, RoutedEventArgs e)
{
try
{
Result = Calc(ResultTextBox.Text);
ResultTextBox.Text = Result.Value.ToString(Constants.InternalPrecisionFormat);
}
catch
{
Result = null;
ResultTextBox.Text = "Ungültig!";
}
}
private void OnResultTextBoxKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
OnEvalClick(sender, null);
}
}
private static decimal Calc(string str)
{
var tokens = Tokenize(str);
var pos = 0;
var result = CalcPlus(tokens, ref pos);
if (pos != tokens.Count)
{
throw new Exception();
}
return result;
}
private static decimal CalcPlus(List<string> tokens, ref int pos)
{
var result = CalcMul(tokens, ref pos);
while (true)
{
// + or - might follow
if (pos >= tokens.Count)
{
break;
}
var op = tokens[pos];
if (op != "+" && op != "-")
{
break;
}
++pos;
var result2 = CalcMul(tokens, ref pos);
if (op == "+")
{
result += result2;
}
else
{
result -= result2;
}
}
return result;
}
private static decimal CalcMul(List<string> tokens, ref int pos)
{
var result = CalcSimple(tokens, ref pos);
while (true)
{
// * or / might follow
if (pos >= tokens.Count)
{
break;
}
var op = tokens[pos];
if (op != "*" && op != "/")
{
break;
}
++pos;
var result2 = CalcSimple(tokens, ref pos);
if (op == "*")
{
result *= result2;
}
else
{
result /= result2;
}
}
return result;
}
private static decimal CalcSimple(List<string> tokens, ref int pos)
{
var token = tokens[pos];
++pos;
decimal result;
switch (token)
{
case "(":
result = CalcPlus(tokens, ref pos);
if (tokens[pos] != ")")
throw new Exception();
++pos;
return result;
case "ceil":
result = CalcPlus(tokens, ref pos);
return Math.Round(result, 2);
default:
return decimal.Parse(token);
}
}
private static List<string> Tokenize(string str)
{
var tokens = new List<string>();
var token = "";
var i = 0;
while (i < str.Length)
{
var c = str[i];
++i;
switch (c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case ',':
token += c;
break;
case '+':
case '-':
case '*':
case '/':
case '(':
case ')':
if (token != "")
{
tokens.Add(token);
}
tokens.Add(c.ToString());
token = "";
break;
default:
// Must be ceil
if (token != "")
{
tokens.Add(token);
}
if (str.Substring(i - 1, 4) == "ceil")
{
tokens.Add("ceil");
i += 3;
}
else
{
throw new Exception();
}
break;
}
}
if (token != "")
{
tokens.Add(token);
}
return tokens;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnApplyClick(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void OnAddDiscountClick(object sender, RoutedEventArgs e)
{
Details.DiscountsInUnits.Add();
}
private void OnDiscountDeleteClick(object sender, RoutedEventArgs e)
{
var discount = (DiscountProperty) ((HighlightButton) sender).DataContext;
Details.DiscountsInUnits.Remove(discount);
}
private void OnClosed(object sender, EventArgs e)
{
Details.TotalAmount.PropertyChanged -= DetailsPropertyChanged;
Details.UnitCount.PropertyChanged -= DetailsPropertyChanged;
Details.DiscountsInUnits.CollectionChanged -= DetailsDiscountChanged;
foreach (var discount in Details.DiscountsInUnits)
{
discount.PropertyChanged -= DetailsPropertyChanged;
}
}
}
}
+35 -2
View File
@@ -177,9 +177,41 @@
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<DockPanel> <DockPanel>
<controls:HighlightButton DockPanel.Dock="Right" <Grid DockPanel.Dock="Right">
HighlightForeground="Red" <Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<controls:HighlightButton
HighlightForeground="{x:Static local:Constants.SubMainColor}"
HighlightBackground="{x:Static local:Constants.MainColor}"
Grid.Row="0"
ToolTip="Details und Hilfswerkzeuge zur Berechnung"
Click="OnCostEntryDetailsClick">
<Viewbox Width="24"
Height="24"
Margin="8, 12, 8, 12"
Stretch="Uniform">
<Canvas Width="512" Height="512">
<Canvas.RenderTransform>
<TranslateTransform X="0" Y="0" />
</Canvas.RenderTransform>
<Path>
<Path.Data>
<PathGeometry
Figures="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"
FillRule="NonZero" />
</Path.Data>
</Path>
</Canvas>
</Viewbox>
</controls:HighlightButton>
<controls:HighlightButton HighlightForeground="Red"
HighlightBackground="White" HighlightBackground="White"
Grid.Row="1"
Click="OnCostEntryDeleteClick"> Click="OnCostEntryDeleteClick">
<Viewbox Width="24" <Viewbox Width="24"
Height="24" Height="24"
@@ -199,6 +231,7 @@
</Canvas> </Canvas>
</Viewbox> </Viewbox>
</controls:HighlightButton> </controls:HighlightButton>
</Grid>
<StackPanel Margin="0, 8, 8, 8"> <StackPanel Margin="0, 8, 8, 8">
<DockPanel Margin="0, 0, 0, 2"> <DockPanel Margin="0, 0, 0, 2">
+11
View File
@@ -51,5 +51,16 @@ namespace UCalc
{ {
Cost.Entries.Add(); Cost.Entries.Add();
} }
private void OnCostEntryDetailsClick(object sender, RoutedEventArgs e)
{
var entry = (CostEntryProperty) ((HighlightButton) sender).DataContext;
var detailsWindow = new CostEntryDetailsWindow(entry.Details) {Owner = this};
if (detailsWindow.ShowDialog() == true)
{
entry.Amount.Value = detailsWindow.Result!.Value.ToString(Constants.DisplayPrecisionFormat);
}
}
} }
} }
+2
View File
@@ -17,6 +17,8 @@ namespace UCalc.Models
public void Add() public void Add()
{ {
using var validator = Model.BeginValidation(true);
var entry = new CostEntryProperty(Model, this, new CostEntry()); var entry = new CostEntryProperty(Model, this, new CostEntry());
entry.StartDate.Value = null; entry.StartDate.Value = null;
entry.EndDate.Value = null; entry.EndDate.Value = null;
+132 -2
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using UCalc.Controls; using UCalc.Controls;
using UCalc.Data; using UCalc.Data;
@@ -69,19 +70,148 @@ namespace UCalc.Models
} }
} }
public class TotalAmountProperty : PositiveDecimalProperty
{
public TotalAmountProperty(Model model, Property parent, string name, decimal value) : base(model, parent, name,
value)
{
}
protected override string ValidateValue()
{
var entry = (CostEntryDetailsProperty) Parent;
try
{
var error = base.ValidateValue();
if (error != "")
{
return error;
}
if (((entry.UnitCount.ConvertedValue ?? 1) != 0 || entry.DiscountsInUnits.Count > 0) &&
ConvertedValue == 0)
{
return $"{Name}: Der Wert muss größer als 0 sein.";
}
return "";
}
finally
{
using var validator = Model.BeginValidation();
validator.Validate(entry.UnitCount);
}
}
}
public class UnitCountProperty : PositiveMoreExactDecimalProperty
{
public UnitCountProperty(Model model, Property parent, string name, decimal value) : base(model, parent, name,
value, Constants.InternalPrecision)
{
}
protected override string ValidateValue()
{
var entry = (CostEntryDetailsProperty) Parent;
try
{
var error = base.ValidateValue();
if (error != "")
{
return error;
}
if (((entry.TotalAmount.ConvertedValue ?? 1) != 0 || entry.DiscountsInUnits.Count > 0) &&
ConvertedValue == 0)
{
return $"{Name}: Der Wert muss größer als 0 sein.";
}
return "";
}
finally
{
using var validator = Model.BeginValidation();
validator.Validate(entry.TotalAmount);
}
}
}
public class DiscountProperty : PositiveDecimalProperty
{
public DiscountProperty(Model model, Property parent, string name, decimal value) : base(model, parent, name,
value)
{
}
protected override string ValidateValue()
{
var error = base.ValidateValue();
return error;
}
}
public class DiscountsProperty : MultiProperty<DiscountProperty>
{
public DiscountsProperty(Model model, Property parent, IEnumerable<decimal> data) : base(model, parent)
{
foreach (var discount in data)
{
Add(new DiscountProperty(Model, this, "Abzug", discount));
}
}
public void Add()
{
using var validator = Model.BeginValidation();
Add(new DiscountProperty(Model, this, "Abzug", 0));
validator.Validate(((CostEntryDetailsProperty) Parent).UnitCount);
}
public new void Remove(DiscountProperty discount)
{
using var validator = Model.BeginValidation();
base.Remove(discount);
validator.Validate(((CostEntryDetailsProperty) Parent).UnitCount);
}
}
public class CostEntryDetailsProperty : NestedProperty
{
public TotalAmountProperty TotalAmount { get; }
public UnitCountProperty UnitCount { get; }
public DiscountsProperty DiscountsInUnits { get; }
public CostEntryDetailsProperty(Model model, Property parent, CostEntryDetails data) : base(model, parent)
{
TotalAmount = Add(new TotalAmountProperty(model, this, "Gesamtbetrag", data.TotalAmount));
UnitCount = Add(new UnitCountProperty(model, this, "Gesamtverbrauch", data.UnitCount));
DiscountsInUnits = Add(new DiscountsProperty(model, this, data.DiscountsInUnits));
}
}
public class CostEntryProperty : NestedProperty public class CostEntryProperty : NestedProperty
{ {
public DateProperty StartDate { get; } public DateProperty StartDate { get; }
public EndDateProperty EndDate { get; } public EndDateProperty EndDate { get; }
public PositiveDecimalProperty Amount { get; } public PositiveDecimalProperty Amount { get; }
public CostEntryDetailsProperty Details { get; }
// TODO: Details
public CostEntryProperty(Model model, Property parent, CostEntry data) : base(model, parent) public CostEntryProperty(Model model, Property parent, CostEntry data) : base(model, parent)
{ {
StartDate = Add(new DateProperty(model, this, "Startdatum", data.StartDate)); StartDate = Add(new DateProperty(model, this, "Startdatum", data.StartDate));
EndDate = Add(new EndDateProperty(model, this, "Enddatum", data.EndDate)); EndDate = Add(new EndDateProperty(model, this, "Enddatum", data.EndDate));
Amount = Add(new PositiveDecimalProperty(model, this, "Betrag", data.Amount)); Amount = Add(new PositiveDecimalProperty(model, this, "Betrag", data.Amount));
Details = Add(new CostEntryDetailsProperty(model, this, data.Details));
} }
} }
} }
+24 -10
View File
@@ -14,6 +14,7 @@ namespace UCalc.Models
{ {
private readonly Model _model; private readonly Model _model;
private int _counter; private int _counter;
private int _postPoneCounter;
private readonly HashSet<Property> _validated; private readonly HashSet<Property> _validated;
private readonly HashSet<Tuple<Property, string>> _notifications; private readonly HashSet<Tuple<Property, string>> _notifications;
@@ -49,7 +50,7 @@ namespace UCalc.Models
return; return;
} }
if (_validated.Add(property) && !_model._loading) if (_validated.Add(property) && _postPoneCounter == 0)
{ {
property.Validate(); property.Validate();
} }
@@ -63,15 +64,33 @@ namespace UCalc.Models
} }
} }
public void IncValidationCounter() public void IncValidationCounter(bool postPone)
{ {
++_counter; ++_counter;
if (postPone || _postPoneCounter != 0)
{
++_postPoneCounter;
}
} }
public void Dispose() public void Dispose()
{ {
--_counter; --_counter;
if (_postPoneCounter > 0)
{
--_postPoneCounter;
if (_postPoneCounter == 0)
{
var queue = new List<Property>(_validated);
_validated.Clear();
ValidateRange(queue);
}
}
if (_counter == 0) if (_counter == 0)
{ {
_validated.Clear(); _validated.Clear();
@@ -140,7 +159,6 @@ namespace UCalc.Models
} }
private readonly Validator _validator; private readonly Validator _validator;
private readonly bool _loading;
public DateTime StartDate { get; } public DateTime StartDate { get; }
public DateTime EndDate { get; } public DateTime EndDate { get; }
public BillingProperty Root { get; } public BillingProperty Root { get; }
@@ -151,17 +169,13 @@ namespace UCalc.Models
StartDate = billing.StartDate; StartDate = billing.StartDate;
EndDate = billing.EndDate; EndDate = billing.EndDate;
_loading = true; using var validator = BeginValidation(true);
Root = new BillingProperty(this, null, billing); Root = new BillingProperty(this, null, billing);
_loading = false;
using var validator = BeginValidation();
validator.Validate(Root);
} }
public Validator BeginValidation() public Validator BeginValidation(bool postPone = false)
{ {
_validator.IncValidationCounter(); _validator.IncValidationCounter(postPone);
return _validator; return _validator;
} }
+28 -8
View File
@@ -6,6 +6,7 @@ using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using UCalc.Annotations; using UCalc.Annotations;
using UCalc.Controls;
namespace UCalc.Models namespace UCalc.Models
{ {
@@ -181,30 +182,49 @@ namespace UCalc.Models
} }
} }
public class PositiveDecimalProperty : ValueProperty<string> public class PositiveMoreExactDecimalProperty : ValueProperty<string>
{ {
public PositiveDecimalProperty(Model model, Property parent, string name, decimal value) : base(model, parent, private readonly int _decimals;
name, public decimal? ConvertedValue { get; private set; }
value.ToString(Constants.DecimalFormat))
public PositiveMoreExactDecimalProperty(Model model, Property parent, string name, decimal value, int decimals)
: base(model, parent, name, value.ToString(Helpers.PrecisionToFormat(decimals, decimals - 2)))
{ {
_decimals = decimals;
} }
protected override string ValidateValue() protected override string ValidateValue()
{ {
if (!decimal.TryParse(Value, out var n) || n < 0) ConvertedValue = null;
if (!decimal.TryParse(Value, out var n))
{ {
return $"{Name}: Der eingegebene Wert ist kein gültiger Betrag."; return $"{Name}: Der eingegebene Wert ist ungültig.";
} }
if (Math.Round(n, 2) != n) if (n < 0)
{ {
return $"{Name}: Der eingegebene Wert besitzt mehr als 2 Nachkommastellen."; return $"{Name}: Der eingegebene Wert darf nicht negativ sein.";
} }
if (Math.Round(n, _decimals) != n)
{
return $"{Name}: Der eingegebene Wert besitzt mehr als {_decimals} Nachkommastellen.";
}
ConvertedValue = n;
return ""; return "";
} }
} }
public class PositiveDecimalProperty : PositiveMoreExactDecimalProperty
{
public PositiveDecimalProperty(Model model, Property parent, string name, decimal value) : base(model, parent,
name, value, Constants.DisplayPrecision)
{
}
}
public abstract class NestedProperty : Property public abstract class NestedProperty : Property
{ {
private readonly List<Property> _properties; private readonly List<Property> _properties;
+6
View File
@@ -46,6 +46,9 @@
<Page Update="CostWindow.xaml"> <Page Update="CostWindow.xaml">
<Generator></Generator> <Generator></Generator>
</Page> </Page>
<Page Update="CostEntryDetailsWindow.xaml">
<Generator></Generator>
</Page>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -88,6 +91,9 @@
<Compile Update="CostWindow.xaml.cs"> <Compile Update="CostWindow.xaml.cs">
<DependentUpon>CostWindow.xaml</DependentUpon> <DependentUpon>CostWindow.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="CostEntryDetailsWindow.xaml.cs">
<DependentUpon>CostEntryDetailsWindow.xaml</DependentUpon>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>