Added cost details window.
This commit is contained in:
+8
-1
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Windows.Media;
|
||||
using UCalc.Controls;
|
||||
using UCalc.Data;
|
||||
|
||||
namespace UCalc
|
||||
@@ -11,9 +12,15 @@ namespace UCalc
|
||||
public static readonly SolidColorBrush MainColor = Brushes.White;
|
||||
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 static readonly string InternalPrecisionFormat =
|
||||
Helpers.PrecisionToFormat(InternalPrecision, InternalPrecision - 2);
|
||||
|
||||
public static readonly string DisplayPrecisionFormat = Helpers.PrecisionToFormat(DisplayPrecision);
|
||||
|
||||
public static readonly ImmutableList<string> SalutationStrs =
|
||||
((Salutation[]) Enum.GetValues(typeof(Salutation))).Select(value => value.AsString()).ToImmutableList();
|
||||
|
||||
|
||||
@@ -37,5 +37,10 @@ namespace UCalc.Controls
|
||||
{
|
||||
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)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
@@ -177,9 +177,41 @@
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<DockPanel>
|
||||
<controls:HighlightButton DockPanel.Dock="Right"
|
||||
HighlightForeground="Red"
|
||||
<Grid DockPanel.Dock="Right">
|
||||
<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"
|
||||
Grid.Row="1"
|
||||
Click="OnCostEntryDeleteClick">
|
||||
<Viewbox Width="24"
|
||||
Height="24"
|
||||
@@ -199,6 +231,7 @@
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</controls:HighlightButton>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Margin="0, 8, 8, 8">
|
||||
<DockPanel Margin="0, 0, 0, 2">
|
||||
|
||||
@@ -51,5 +51,16 @@ namespace UCalc
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@ namespace UCalc.Models
|
||||
|
||||
public void Add()
|
||||
{
|
||||
using var validator = Model.BeginValidation(true);
|
||||
|
||||
var entry = new CostEntryProperty(Model, this, new CostEntry());
|
||||
entry.StartDate.Value = null;
|
||||
entry.EndDate.Value = null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UCalc.Controls;
|
||||
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 DateProperty StartDate { get; }
|
||||
public EndDateProperty EndDate { get; }
|
||||
public PositiveDecimalProperty Amount { get; }
|
||||
|
||||
// TODO: Details
|
||||
public CostEntryDetailsProperty Details { get; }
|
||||
|
||||
public CostEntryProperty(Model model, Property parent, CostEntry data) : base(model, parent)
|
||||
{
|
||||
StartDate = Add(new DateProperty(model, this, "Startdatum", data.StartDate));
|
||||
EndDate = Add(new EndDateProperty(model, this, "Enddatum", data.EndDate));
|
||||
Amount = Add(new PositiveDecimalProperty(model, this, "Betrag", data.Amount));
|
||||
Details = Add(new CostEntryDetailsProperty(model, this, data.Details));
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-10
@@ -14,6 +14,7 @@ namespace UCalc.Models
|
||||
{
|
||||
private readonly Model _model;
|
||||
private int _counter;
|
||||
private int _postPoneCounter;
|
||||
private readonly HashSet<Property> _validated;
|
||||
private readonly HashSet<Tuple<Property, string>> _notifications;
|
||||
|
||||
@@ -49,7 +50,7 @@ namespace UCalc.Models
|
||||
return;
|
||||
}
|
||||
|
||||
if (_validated.Add(property) && !_model._loading)
|
||||
if (_validated.Add(property) && _postPoneCounter == 0)
|
||||
{
|
||||
property.Validate();
|
||||
}
|
||||
@@ -63,15 +64,33 @@ namespace UCalc.Models
|
||||
}
|
||||
}
|
||||
|
||||
public void IncValidationCounter()
|
||||
public void IncValidationCounter(bool postPone)
|
||||
{
|
||||
++_counter;
|
||||
|
||||
if (postPone || _postPoneCounter != 0)
|
||||
{
|
||||
++_postPoneCounter;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
--_counter;
|
||||
|
||||
if (_postPoneCounter > 0)
|
||||
{
|
||||
--_postPoneCounter;
|
||||
|
||||
if (_postPoneCounter == 0)
|
||||
{
|
||||
var queue = new List<Property>(_validated);
|
||||
_validated.Clear();
|
||||
|
||||
ValidateRange(queue);
|
||||
}
|
||||
}
|
||||
|
||||
if (_counter == 0)
|
||||
{
|
||||
_validated.Clear();
|
||||
@@ -140,7 +159,6 @@ namespace UCalc.Models
|
||||
}
|
||||
|
||||
private readonly Validator _validator;
|
||||
private readonly bool _loading;
|
||||
public DateTime StartDate { get; }
|
||||
public DateTime EndDate { get; }
|
||||
public BillingProperty Root { get; }
|
||||
@@ -151,17 +169,13 @@ namespace UCalc.Models
|
||||
StartDate = billing.StartDate;
|
||||
EndDate = billing.EndDate;
|
||||
|
||||
_loading = true;
|
||||
using var validator = BeginValidation(true);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UCalc.Annotations;
|
||||
using UCalc.Controls;
|
||||
|
||||
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,
|
||||
name,
|
||||
value.ToString(Constants.DecimalFormat))
|
||||
private readonly int _decimals;
|
||||
public decimal? ConvertedValue { get; private set; }
|
||||
|
||||
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()
|
||||
{
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
private readonly List<Property> _properties;
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
<Page Update="CostWindow.xaml">
|
||||
<Generator></Generator>
|
||||
</Page>
|
||||
<Page Update="CostEntryDetailsWindow.xaml">
|
||||
<Generator></Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -88,6 +91,9 @@
|
||||
<Compile Update="CostWindow.xaml.cs">
|
||||
<DependentUpon>CostWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="CostEntryDetailsWindow.xaml.cs">
|
||||
<DependentUpon>CostEntryDetailsWindow.xaml</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user