Форматирование строк C# WPF
При работе со строками столкнулся с проблемой форматирования. Есть класс параметра, в котором содержится свойство Value строкового формата.
При изменении, свойство валидируется с помощью регулярного выражения на соответствие формату числа int или double (Посредством реализации интерфейса IDataErrorInfo).
Так же в классе параметра имеется метод, который отвечает за само форматирование строки под число. Его задачи:
- Замена входящей точки на запятую
- Устранение пробелов из строки
- Обрезка лишних нулей в начале строки
XAML-разметка имеет привязку к Value и тут возникает пара интересных моментов:
При использовании UpdateSourseTrigger в значении LostFocus все работает, как и задумано, но если изменить его на PropertyChanged, то я сталкиваюсь со странным поведением:
- Если в начале строки подставить один или несколько нулей, то логика форматирования не применяется. Применяется, только если изменить число в конце строки
- Пробелы в строку подставляются, как ни в чем не бывало
- Если поставить в начале строки точку, то валидация перестает работать вообще (Даже в случае с LostFocus)
Мне нужно использовать триггер на изменение свойства.
Подскажите, пожалуйста, что я делаю не так? И как вообще рекомендуется решать подобные задачи?
MainWindow.xaml:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Border BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontSize="30" FontWeight="Bold" Text="Value: "/>
<TextBox Grid.Column="1" FontSize="30" Width="100"
TextAlignment="Center"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"
Text="{Binding Parameter.Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
<!--Кнопка просто для потери фокуса-->
<Button Grid.Column="2" Width="70" Content="Focus off"/>
</Grid>
</Border>
MainViewModel.cs:
public class MainViewModel : ViewModelBase
{
public Parameter Parameter { get; set; }
public MainViewModel()
{
Parameter = new Parameter(55);
}
}
Parameter.cs:
public class Parameter : ViewModelBase, IDataErrorInfo
{
private const string DoubleRegex = "^[0-9]*[.,]?[0-9]+$";
private string _value;
public string Value
{
get => _value;
set
{
_value = Format(value);
RaisePropertyChanged(nameof(Value));
}
}
public string Error => null;
public string this[string propertyName]
{
get
{
string error = null;
if (propertyName == nameof(Value))
{
if (!Regex.IsMatch(Value, DoubleRegex))
{
error = "Введите число";
}
}
return error;
}
}
public Parameter(double number)
{
Value = number.ToString();
}
private string Format(string str)
{
var replaced = str.Replace('.', ',').Trim();
var nullsNumber = 0;
foreach (var letter in replaced)
{
if (letter.Equals('0'))
{
nullsNumber++;
}
else
{
break;
}
}
int cutTo;
if (replaced[nullsNumber].Equals(','))
{
cutTo = nullsNumber - 1;
}
else
{
cutTo = nullsNumber;
}
var result = replaced.Substring(cutTo);
return result;
}
}
Ответы (1 шт):
Вы пытаетесь перемешать в кучу валидацию и конвертацию. Давайте немного проясним.
- Привязываться надо к свойствам нужного типа. Никаких промежуточных костылей не должно быть.
UpdateSourceTrigger=PropertyChangedи правда мешает ввести точку, если привязка идет к числовому полю, например кdouble. Это лечится изменением поведения WPF.- Далее, валидация должна быть отдельно, конвертация отдельно. Если они подчиняются общей логике, можно завести например статический класс, который будет содержать нужный обоим метод.
IDataErrorInfoподходит только для уже распознанных данных, то есть когдаdoubleуже валидный, но вы хотите например наложить ограничение, чтобы он был к примеру положительным числом.
Дисклеймер: применять толерантность точка-запятая с числах с плавающей точкой - плохая идея. Какой дробный разделитель использовать - ответственность операционной системы, а не вашего кода. Так что из ниже предложенного решения я бы выкинул конвертер и .Replace.Replace из правила валидации. То что я использовал такие приемы вопреки здравому смылу - это просто чтобы показать вам, как возможно сделать то, что у вас происано в условиях вопроса.
Настройка
Переопределение поведения WPF, чтобы оно пропускало ввод точки при привязке к double с применением UpdateSourceTrigger=PropertyChanged
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
base.OnStartup(e);
}
}
Конвертация
Хотите точки на запятые менять и пробелы удалять - пожалуйста
public class DoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is double number ? number.ToString("G") : string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is string input))
throw new ArgumentException($"DoubleConverter.ConvertBack получил неверный тип {value.GetType().Name}, но ожидал {typeof(string).Name}");
if (double.TryParse(input.Replace(".", ",").Replace(" ", ""), out double number))
return number;
else
return null;
}
}
И подключить конвертер
<Window.Resources>
<local:DoubleConverter x:Key="DoubleConverter"/>
</Window.Resources>
Валидация
Стандартное сообщение Value cannot 'sdfsd' be converted не прикольное, хочется своё, это можно переопределить создав своё правило валидации.
public class DoubleValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value is string input && double.TryParse(input.Replace(".", ",").Replace(" ", ""), out _))
return ValidationResult.ValidResult;
else
return new ValidationResult(false, "Введите число");
}
}
Разметка
<TextBox Grid.Column="1" FontSize="30" Width="100"
TextAlignment="Center"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}">
<TextBox.Text>
<Binding Path="Parameter.Value" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:DoubleValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
View Model
В результате того, что все поставленные вами условия в вопросе я выполнил выше, интерфейс IDataErrorInfo я просто выбрасываю
public class Parameter : ViewModelBase
{
private double _value;
public double Value
{
get => _value;
set
{
_value = value;
RaisePropertyChanged(nameof(Value));
}
}
public Parameter(double number)
{
Value = number;
}
}
