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.
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
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.
HI Paladin,
vielen Dank für deine Hilfe - so funktioniert es nun 😃
Den Satz hab ich leider erst übersehen.
Was heißt denn "funktioniert nicht"?
Ehrlich gesagt: ich bin selbst am Rätseln! Ich hätte beinahe zurückgeschrieben, dass es tatsächlich nicht funktioniert - und zwar hatte ich mein xaml-Code entsprechend deinen Ausführungen abgeändert, trotzdem wurde die View nicht angezeigt. Keine Fehlermeldung kein gar nichts - nur die View wurde nicht angezeigt. Ich habe dann allerdings - einfach versuchsweise - meine anderen beiden Views mal eingesetzt, z. B. so:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles/ColorPaletteStyles.xaml"/>
<!-- ... -->
<ResourceDictionary>
<DataTemplate DataType="{x:Type viewModels:OverviewViewViewModel}">
<views:OverviewVieww/>
</DataTemplate>
<!-- ... -->
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
--> und es hat auf Anhieb funktioniert! Ich konnte und kann mir nicht erklären, warum es mit der DefaultView einfach nicht klappen wollte. Ich weiß aber noch, dass ich gestern beim Erstellen der DefaultView eine Fehlermeldung bekommen habe, gleich nach dem Erstellen: ich kann den Inhalt leider nicht wiedergeben - ich wurde lediglich im DEsigner aufgefordert die Projektmappe neu zu erstellen. Nachdem der Inhalt der xaml im Designer einwandfrei dargestellt wurde und auch sonst alles lief, dachte ich, der Fehler hätte sich mit dem neu erstellen des Projekts erledigt. Ich kann nicht die Hand dafür ins Feuer legen, dass das tatsächlich der Grund ist, aber: ich habe den xaml-Code der DefaultView einfach kopiert, die xaml-Datei gelöscht und erneut eine DefaultView erstellt. Keine Fehlermeldung diesmal - alles klappt jetzt einwandfrei!
Deine Hilfe hat mir viele Kopfschmerzen hier erspart: ich hätte den Fehler sonstwo gesucht.
Gruß
Vorph
Hallo,
nach langer Pause, versuche ich mich derzeit wieder an einer WPF-App. Da ich auf meiner MainView einen Content-Bereich habe, mit dem ich zu den einzelnen Views navigieren kann, benutze ich die ApplicationResources, um dort DataTemplates für meine Views zu hinterlegen. Das sieht dann so aus:
<Application x:Class="AppStart.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:Views;assembly=Views"
xmlns:viewModels="clr-namespace:MainViewModels;assembly=ViewModels"
xmlns:local="clr-namespace:AppStart"
StartupUri="pack://application:,,,/Views;component/MainView.xaml">
<Application.Resources>
<DataTemplate DataType="{x:Type viewModels:DefaultViewViewModel}">
<views:DefaultView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:OverviewViewModel}">
<views:OverviewView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:DetailViewModel}">
<views:DetailView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SummaryViewViewModel}">
<views:SummaryView/>
</DataTemplate>
</Application.Resources>
</Application>
Nun möchte ich allerdings Styles implementieren, auf die in der ganzen App zugegriffen werden soll (für Textboxen, Textblocks, Comboboxen, etc.). Wenn ich das so in meine App.xaml verpacke...
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--Order is important-->
<ResourceDictionary Source="Styles/ColorPaletteStyles.xaml"/>
<ResourceDictionary Source="Styles/ButtonStyles.xaml"/>
<ResourceDictionary Source="Styles/CheckBoxStyles.xaml"/>
<ResourceDictionary Source="Styles/ComboBoxStyles.xaml"/>
<ResourceDictionary Source="Styles/RadioButtonStyles.xaml"/>
<ResourceDictionary Source="Styles/TextBlockStyles.xaml"/>
<ResourceDictionary Source="Styles/TextBoxStyles.xaml"/>
<ResourceDictionary Source="Styles/MenuStyle.xaml"/>
<ResourceDictionary Source="Styles/TabControlStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
...funktioniert das auch einwandfrei. Allerdings - meine DataTemplates, die ich für die View-Navigation brauche kann ich nun nicht mehr wie bisher hinzufügen. Also folgendes funktioniert nicht:
<Application.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type viewModels:DefaultViewViewModel}">
<views:DefaultView/>
</DataTemplate>
<ResourceDictionary.MergedDictionaries>
<!--Order is important-->
<ResourceDictionary Source="Styles/ColorPaletteStyles.xaml"/>
<ResourceDictionary Source="Styles/ButtonStyles.xaml"/>
[...]
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Auch das hier funktioniert nicht:
<Application.Resources>
<DataTemplate DataType="{x:Type viewModels:DefaultViewViewModel}">
<views:DefaultView/>
</DataTemplate>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--Order is important-->
<ResourceDictionary Source="Styles/ColorPaletteStyles.xaml"/>
<ResourceDictionary Source="Styles/ButtonStyles.xaml"/>
[...]
Hier bekomme ich wenigstens einen Hinweis:
Jeder Verzeichniseintrag muss einen zugewiesenen Schlüssel aufweisen.
Auch damit komme ich nicht weiter: zwar verschwindet der Hinweis, wenn ich einen x:Key hinzufüge, allerdings müsste ich dann jedesmal wenn ich die Dienste des DataTemplates benötige das jeweilige Template explizit mit Key ansprechen, was aber beim Navigieren eher nicht geht (bzw. ich weiß nicht ob es überhaupt einen Weg gibt, das so zu bewerkstelligen...).
Die Essenz der langen Vorrede: ich bin sicher, es gibt einen einfachen Weg, damit meine DataTemplates und die ResourceDictionaries in der App.xaml friedlich koexistieren können. Genau hier brauche ich Hilfe: wie bewerkstellige ich das?
Vielen Dank,
beste Grüße
Vorph
--> ich habe bereits versucht die DataTemplates in ein REsourceDictionary auszulagern - leider funktioniert auch das scheinbar nicht.
Ah, sorry, ich wollte eigentlich noch den Code posten, falls mal jemand anderes ein ähnliches Problem hat und da nicht weiter kommt.
var colorItems = typeof(Colors).GetProperties();
cmbFontColors.ItemsSource = colorItems;
var defaultColor = colorItems.Where(c => c.Name == "Black").FirstOrDefault();
cmbFontColors.SelectedItem = defaultColor;
Wie Palladin es bereits erwähnt hat: beim Lesen des Codes wird nicht gleich ersichtlich warum, wieso, weshalb.
@Palladin007 Herzlichen Dank für deine Antwort und die Mühe die du dir gemacht hast, auch bezüglich der Erklärungen. Sehr cool - dadurch hatte ich beim Lesen das ein oder andere "Aha"-Erlebnis! Auf jeden Fall weiß ich jetzt, was "under the hood" passiert, und damit ist das Ganze schon etwas demystifiziert.
Bleibt bei meiner Lösung einzig ein kleiner "Schönheitsfehler": wenn ich einen Default setzen möchte (z. B. cmbFontColors.SelectedItem = [...]) scheitere ich mit dem Ausdruck hinter dem Gleichheitszeichen - so wie ich das jetzt verstanden habe müsste da ja auch ein PropertyInfo-Item hin. Das ließe sich über Zwischenschrite bewerkstelligen, hat aber den großen Nachteil, dass es sehr "unsexy" ist - da man Farben eher nicht mit PropertyInfos in Verbindung bringt.
Jetzt bin ich zwar reiner Hobby-Coder, aber ich frage mich, ob ich in zwei, drei Jahren direkt Blicke, warum da kein Brush drinne ist. Ich merke, ich entwickle gerade große Sympathie für die Zwischenklassenlösung 😃
Also: die gute Nachricht ist, dass ich über Umwege zu einer Lösung gekommen bin. Verstehen tu ich's nicht, aber das steht ja auf einem anderen Blatt.
Ich weiß auch nicht, ob es wirklich eine Lösung oder letztlich ein Hack ist - also bitte nicht gleich schlagen, falls ich hier Wurgs-Code geschrieben habe!
Da der Compiler ja eine InvalidCastException wirft, dachte ich zuerst, ich caste zum falschen Type - so hatte ich die Fehlermeldung ursprünglich verstanden. Man kann es wohl auch als "Aufruf" zum Casten in den Type PropertyInfo vestehen. Was ich jetzt letztlich getan habe. Darüber erhalte ich den Farbnamen - als string. Und das funktioniert. Warum auch immer. Hier der Code:
var propertyInfo = (PropertyInfo)e.AddedItems[0];
var editValue = propertyInfo.Name; //editValue ist hier effektiv ein string
ApplyPropertyValueToSelectedText(TextElement.ForegroundProperty, editValue);
Weil ich es erst nicht glauben konnte, habe ich editValue durch "Red" ersetzt - auch das funktioniert.
Leute, vielen Dank erstmal! Im neuen Jahr nehme ich dann die übrigen Fragen zum Thema Texteditor in Angriff - jetzt bin ich erstmal froh, dass ich prinzipiell Schriftart, Schriftgröße und Schriftfarbe einstellen kann. Die Details dann im neuen Jahr.
Ich wünsche euch einen guten Rutsch - man sieht sich!
Viele Grüße
Vorph
Hallo Hans,
ja, das weiß ich schon - das waren lediglich verzweifelte Versuche, die freilich von vornherein zum Scheitern verurteilt waren. Entweder ich stehe voll auf dem Schlauch oder... Keine Idee?
Benutze ich den falschen Type? Oder stimmt generell an meinem Vorgehen etwas nicht?
Ich habe versuchsweise im ViewModel mal ein Property mit dem Namen DefaultColor erstellt, das ich an die Foreground-Property der RichTextBox gebunden habe:
DefaultColor = new SolidColorBrush(Colors.Red);
Die App lädt - die Schrift ist rot. Voila.
Ich versteh's einfach nicht, warum das mit dem Color-Picker nicht geht. Ich fülle den mit
cmbFontColors.ItemsSource = typeof(Colors).GetProperties();
Liegt hier vielleicht erkennbar ein Fehler? Ich meine, ich bekomme die Farben wunderbar angezeigt in meiner ComboBox, aber das mit dem SelectedItem funktioniert nicht. Zumindest nicht im Codebehind. Aber ja anscheinend auch im ViewModel nicht richtig, zumindest nicht, solange das SelectedItem der ComboBox im Spiel ist.
Hm...das ist mehr als seltsam:
private void cmbFontColors_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var dim = "dim";
try
{
//Brushes editValue = (Brushes)e.AddedItems[0];
//ApplyPropertyValueToSelectedText(TextElement.ForegroundProperty, editValue);
ApplyPropertyValueToSelectedText(TextElement.ForegroundProperty, Brushes.Red);
}
catch (Exception) { }
So funktioniert es, meine Methode wird aufgerufen, die Schriftfarbe ist Rot. Wie du siehst hatte ich vorher versucht das e.AddedItems-Objekt zu Brushes zu casten - und wieder passiert nichts. Zur Runtime kann ich zwar eine beliebige Farbe aus der Combo wählen. Untersuche ich aber meinen Code mittels Breakpoint, bekomme ich an genau dieser Stelle eine Fehlermeldung. Steh' ich auf dem Schlauch? Es müsste doch so eigentlich funktionieren, oder?
Übrigens: trotz Focus(); in der anderen Methode bleibt es dabei: zuerst muss ich was schreiben - wenn ich dann die Farbe ändere klappt es. Wenn ich im Konstruktor Focus() verwende scheint das nichts zu ändern. Erst schreiben, dann lassen sich Schriftgröße und Schriftart ändern. Aber das betrachtet ich als gesondertes Problem - ich versuche erstmal das mit der FontColor in den Griff zu bekommen. Dennoch vielen Dank!
EDIT: Habe es gerade noch mit e.AddedItems[0] as Brushes (Brush, Colors. SolidColorBrush...) probiert - dann ist editValue einfach null^^ Ebenso, wenn man statt e.AddedItems direkt cmbFontColors anspricht. Lässt sich einfach nicht casten!