Hallo,
ich hänge gerade an einem Problem, das mir einige Rätsel aufgibt. Ich versuche es so einfach wie möglich zu beschreiben:
Ich habe eine TextBox, in die nur die Ziffern von 0-9, sowie ein ',' eingegeben werden können. Ich möchte letztendlich einen Währungsbetrag angeben können. Aus der DB wird ein in meinem ViewModel ein decimal gelesen und der wird von einem ValueConverter in der View als string ausgegeben.
Im ValueConverter rufe ich eine CurrencyConversion-Klasse (im Code untern "ThirdConverter" --> der ungünstige Name steht da, weil ich seit Tagen Fehlersuche betreibe und dies der 3. Konverter ist, mit dem ich versuche das Problem zu lösen...), die aus dem decimal einen string im gewünschten Währungsformat ausgiebt. Ich weiß, es gibt buil-in-Lösungen, aber
Meine Methoden liefern das gewünschte Resultat: ich gebe einen Betrag ein, z. B. 1425,36 und bekomme in der Textbox "1 425,36 €" angezeigt.
Wichtig ist hierbei: nach jeder Eingabe befindet sich das Caret in der Textbox immer an der richigen Stelle - da musste ich auch gar nix machen, das regelt die Textbox selbst.
PROBLEM: Zumindest dachte ich, dass die Textbox selbst regelt. Denn aus unerfindlichen Gründen springt das Caret bei der Eingabe von 1234567891 (bei Eingabe der letzten 1) ganz ans Ende der Selection. Ich kann dann zwar das Caret hinter der 1 positionieren und eine weitere Eingabe machen, ab dann wird es aber immer ganz ans Ende springen (die Formatierung wird dabei aber weiterhin tadellos in 3-er-Gruppen angezeigt).
Nachfolgend mein Code.
Zur Beachtung: er ist aus meinem Test-Projekt eingefügt, d.h. ich habe ihn so einfach wie nur irgend möglich gehalten. Man kann in dieser Version nur Ziffern eingeben, andere Zeichen crashen die Anwendung. Das "springende" Caret ist ja das Problem.
Meine CurrencyConverter-Klasse (heißt hier ThirdConverter --> Gründe s. oben)
public static decimal? ConvertCurrencyStringToDecimal(string? _input)
{
StringBuilder sb = new StringBuilder();
sb.Append(_input.Replace(" ", string.Empty));
return decimal.Parse(sb.ToString());
}
public static string? ConvertDecimalValueToCurrencyString(decimal? _input)
{
var regularValueResult = FinalValueConversion(_input);
return regularValueResult;
}
//Helfer-Methoden
private static string? FinalValueConversion(decimal? value)
{
//Define string builder for output string
StringBuilder preString = new StringBuilder();
StringBuilder arrangedOutput = new StringBuilder();
decimal x = Convert.ToDecimal(value);
preString.Append(x.ToString("0.00"));
if (preString.ToString().Contains(","))
{
arrangedOutput.Append(ArrangeOutputString(preString.ToString()));
}
return arrangedOutput.ToString();
}
private static string TrimOutputString(decimal? value)
{
if (value == null)
{ return null; }
StringBuilder final_pre = new StringBuilder();
StringBuilder final_post = new StringBuilder();
StringBuilder final = new StringBuilder();
decimal _value = Convert.ToDecimal(value); // convet decimal? to decimal...
final.Append(_value.ToString("#.00")); //...make it a string
var vsArray = final.ToString().Split(',');
if (vsArray.Length > 1)
{
if (vsArray[1].Length > 2)
{
final.Clear();
final_post.Append(vsArray[1].Remove(2));
final_pre.Append(vsArray[0]);
final_pre.Append(",");
final.Append(final_post.ToString());
}
}
return final.ToString();
}
#region Arrangement of output string methods
private static string ArrangeOutputString(string _preFinalString)
{
StringBuilder sb = new StringBuilder();
string[] dummyArray = _preFinalString.Split(',');
string preCommaValue = dummyArray[0];
string postCommaValue = dummyArray[1];
var dummyList = ConsolidateDigitGroups(preCommaValue);
string finalString = AssembleGroupsOfThree(dummyList);
sb.Append(finalString).Append(",").Append(postCommaValue);
return sb.ToString();
}
private static List<string> ConsolidateDigitGroups(string _unconsolidatedString) //neueste
{
StringBuilder sb = new StringBuilder();
List<string> tmpList = new List<string>();
int numberOfDigits = _unconsolidatedString.Count();
while (numberOfDigits > 3)
{
//IMPORTANT! Add the LAST 3 digits first. E.g.: 1566 - 566 must be truncated first in order to get the final 1 566.
tmpList.Add(_unconsolidatedString.Substring(_unconsolidatedString.Length - 3));
//Remove the digits that were truncated from the original input.
_unconsolidatedString = _unconsolidatedString.Remove(_unconsolidatedString.Length - 3);
//Lower the number of remaining digits by 3 (i.e. the amount of digits that just were truncated)
numberOfDigits -= 3;
}
//If after subtracting 3 each time the while-loop is true, a rest greater than zero remains, add the remaining strings to the list.
if (numberOfDigits > 0)
{
tmpList.Add(_unconsolidatedString);
}
return tmpList;
}
private static string AssembleGroupsOfThree(List<string> stringChunks)
{
StringBuilder sb = new StringBuilder();
//Define an output string.
string outputString = string.Empty;
//Define a limit for the following for-loop - it is equal to the number of entries in the list.
int limit = stringChunks.Count;
//Define an indexer that will access the entries in the stringChunks-List.
//The indexer will start with the integer which represents the maximum number of entries.
int indexer = limit;
for (int i = 1; i <= limit; i++)
{
//Append the LEAST index of the stringChunks-List. [The the list is 0-based index, the actual indexer is indexer - 1]
//With each iteration the indexer will get closer to 0. --> MUST end with zero!
//outputString += stringChunks[indexer - 1];
sb.Append(stringChunks[indexer - 1]);
//Lower the indexer by 1
indexer--;
//Check, wheter the current iteration is less than the limit. As long as the expression is true, add a grouping placeholder (" ").
if (i < limit)
{
//outputString += " ";
sb.Append(" ");
}
}
//return outputString;
return sb.ToString();
}
#endregion
Hier mein Value-Converter
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Convert a decimal to a string
var resultString = ThirdConverter.ConvertDecimalValueToCurrencyString((decimal)value);
return resultString;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Convert a currencs string to a decimal
var result = ThirdConverter.ConvertCurrencyStringToDecimal(value.ToString());
return result;
}
}
Und mein xaml-code für die View
UserControl x:Class="ValidateAndConvert_Views.TestView"
[...]
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<conversion:TestConverter x:Key="testConverter"/>
</UserControl.Resources>
<Grid Background="LightGray">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- Content -->
<Grid Grid.Row="1">
<TextBox Text="{Binding UserInput, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource testConverter}, ConverterParameter=true}">
</TextBox>
</Grid>
</Grid>
</UserControl>
Lösungsversuche bisher:
Ich habe beim Debuggen Haltepunkte gesetzt (im ValueConverter): dort wird der decimal 1234567891 zu "1 234 567 891,00" konvertiert. Hier springt nun aber das Caret ans Ende. Bei allen vorherigen Ziffern - nicht.
--> Wenn ich die Formatierung weglasse (also das arrangieren in 3-er-Gruppen und das Hinzufügen der Dezimalstellen), dann kann ich natürlich so viel eingeben, wie ich möchte. Trotzdem verstehe ich nicht, wieso es nach Eingabe von 9 Zeichen abrupt nicht mehr funktioniert, zumal weiterhin korrekt formatiert ausgegeben wird, wenn ich das Caret manuell an die richtige Stelle setze.
Hilfen:
Vorab vielen Dank!
viele Grüße
Vorph
Kannst du nicht einfach ein NumberFormatInfo
dafür erstellen (und dies dann einer CultureInfo
zuweisen), s.a. Standardmäßige Zahlenformatzeichenfolgen: Spezifizierer für Währungsformat (C) ?
Ansonsten schau auch mal in WpfCurrencyTextbox rein.
Danke für den Hinweis - so auf den ersten Blick vermute ich, dass ein Großteil der Funktionen, diie ich implementiert habe, dann wegfallen:
Die Eingabe von "," führt in meinem Fall z. B. dazu, dass "0,00 €" abgezeigt werden, das Caret aber an der richtigen Stelle (nach dem Komma) steht. Das macht die TextBox nach der Umwandlung von selbst.
"-", "-0", "-,", "-0,0" werden korrekt als "-0,00 €" interpretiert (mit dem Caret an der Position nach dem Komma, bzw. nach der ersten Null.
Auch das Löschen der Eingaben funktioniert wie intendiert.
Lediglich bei 99 Miliarden 999 Millionen 999 Tausend 999 ist Schluss - wenn ich jetzt noch eine Ziffer eingebe, springt das Caret hinter das Euro-Zeichen. Sonst bleibt es immer vor dem Komma^^
Ich tendiere langsam Richtung Bug: wie bereits erwähnt - im Converter wird sowohl in der Convert-, als auch der ConvertBack-Methode jeweils der korrekte Wert returniert - danach gibt es keine von mir selbst geschriebene Logik mehr, zumal schon gar keine, die die Position des Carets beeinflusst.
Zitat von GeneVorph
Ich tendiere langsam Richtung Bug:
In .NET? Die Wahrscheinlichkeit ist nahezu 0.
Deine verwendeten Technologien sind so dermaßen weit verbreitet, etabliert und stabil, dass quasi jeder Bug bekannt ist - wenn überhaupt hier einer existiert.
Dein Code hat aber mit Sicherheit Bugs, allein das ganze Region-Handling strikt auf Komma etc... auch das ganze StringBuilder-Handling macht so kein wirklichen sinn.
Wo genau nun Dein inhaltlicher Bug ist, sehe ich auch nicht; aber die Wahrscheinlichkeit, dass es an .NET/WPF liegt, ist echt gering.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Hallo Gene,
als erstes würde ich den Converter Code aufräumen, wie es Th69 bereits vorgeschlagen hat. Mit folgendem Snippet sollte dein gewünschtes Ergebnis herauskommen:
NumberFormatInfo info = new()
{
CurrencySymbol = "€",
CurrencyDecimalDigits = 2,
CurrencyGroupSeparator = " ",
CurrencyDecimalSeparator = ",",
CurrencyPositivePattern = 3,
CurrencyNegativePattern = 8
};
decimal value = 123_456_789_012_345.131516m;
string text = value.ToString("c", info); // 123 456 789 012 345,13 €
Zu dem Caret Problem:
Woher sollte die Textbox den Zusammenhang von dem eingegebenen Text und dem dahinterliegenden Converter kennen? Die verlinkte CurrencyTextBox wird mit Sicherheit eine interne Logik haben, die Eingabe und Caret Position speziell behandelt. Womöglich wirst du dies selbst machen müssen, da diese TB nur vorgegebene Formate unterstützt (laut Beschreibung).
Guten Morgen zusammen!
Ähnlich Spooks Ansatz wäre das hier mein Versuch an die Thematik ran zu gehen:
public static string FormatCurrency(decimal input)
{
CultureInfo culture = new CultureInfo("de-DE");
culture.NumberFormat.CurrencyGroupSeparator = " ";
culture.NumberFormat.CurrencySymbol = "€";
string output = string.Format(culture, "{0:C2}", input);
return output;
}
mfg s3nf
(Leben != Ponyhof)