How can I read two values from TextBox separated by some separator using Converter and then bind them to two different properties?

huangapple go评论71阅读模式
英文:

How can I read two values from TextBox separated by some separator using Converter and then bind them to two different properties?

问题

I'm currently working on an interpolation project. I need the user to enter the interpolation boundaries (basically, 2 double values) in one TextBox separated by some separator.

In my MainWindow.xaml.cs, I have created a class ViewData whose fields are controls in the user interface. And assigned my DataContext to it.

In particular, this class has two fields of type double: boundA and boundB. I'd like to be able to take users' input from TextBox and bind the first value to boundA, the second one to boundB. My ViewData class:

UPDATE
I've tried using IMultiValueConverter + MultiBinding but I failed at making it work How can I read two values from TextBox separated by some separator using Converter and then bind them to two different properties?
Here's my MainWindow.xaml:

英文:

I'm currently working on an interpolation project. I need the user to enter the interpolation boundaries (basically, 2 double values) in one TextBox separated by some separator.

In my MainWindow.xaml.cs I have created a class ViewData whose fields are controls in the user interface. And assigned my DataContext to it. Like this:

 public partial class MainWindow : Window
    {
        ViewData viewData = new();
        public MainWindow()
        {
            InitializeComponent();
            DataContext = viewData;
        }

    }

In particular, this class has two fields of type double: boundA and boundB. I'd like to be able to take users input from TextBox and bind first value to boundA, second one to boundB.
My ViewData class:

using System;
using System.Collections.Generic;
using System.Windows;
using CLS_lib;

namespace Splines
{
    public class ViewData
    {
        /* RawData Binding */
        public double boundA {get; set;}
        public double boundB {get; set;}
        public int nodeQnt {get; set;}
        public bool uniform {get; set;}
        public List<FRaw> listFRaw { get; set; }
        public FRaw fRaw { get; set; }
        public RawData? rawData {get; set;}
        public SplineData? splineData {get; set;}

        /* --------------- */
        /* SplineData Binding */
        public int nGrid {get; set;}
        public double leftDer {get; set;}
        public double rightDer {get; set;}
        /* ------------------ */
        public ViewData() {
            boundA = 0;
            boundB = 10;
            nodeQnt = 15;
            uniform = false;
            nGrid = 20;
            leftDer = 0;
            rightDer = 0;
            listFRaw = new List<FRaw>
            {
                RawData.Linear,     
                RawData.RandomInit,  
                RawData.Polynomial3
            };       
            fRaw = listFRaw[0];
        }
        public void ExecuteSplines() {
            try {
                rawData = new RawData(boundA, boundB, nodeQnt, uniform, fRaw);
                splineData = new SplineData(rawData, nGrid, leftDer, rightDer);
                splineData.DoSplines();
            } 
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }

        public override string ToString()
        {
            return $"leftEnd = {boundA}\n" +
                   $"nRawNodes = {nodeQnt}\n" +
                   $"fRaw = {fRaw.Method.Name}" +
                   $"\n"; 
        }
    }
}

UPDATE
I've tried using IMultiValueConverter + MultiBinding but I failed at making it work How can I read two values from TextBox separated by some separator using Converter and then bind them to two different properties?
Here's my MainWindow.xaml:

<userControls:ClearableTextBox x:Name="bounds_tb" Width="250" Height="40" Placeholder="Nodes amount">
    <userControls:ClearableTextBox.Text>
        <MultiBinding Converter="{StaticResource boundConverter_key}" UpdateSourceTrigger="LostFocus" Mode="OneWayToSource">
            <Binding Path="boundA"/>
            <Binding Path="boundB"/>
        </MultiBinding>
    </userControls:ClearableTextBox.Text></userControls:ClearableTextBox>

And my Converter:

    public class BoundariesMultiConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            string boundaries;
            boundaries = values[0] + ";" + values[1];
            return boundaries;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            string[] splitValues = ((string)value).Split(';');
            return splitValues;
        }
    }

答案1

得分: 0

以下是您提供的内容的中文翻译:

提议的解决方案基于一个UserControl,在其代码中执行工作。
让我们从如何使用名为"TextBoxTwoDoubles"的UserControl开始:

<local:TextBoxTwoDoubles
    NumberA="{Binding BoundA, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
    NumberB="{Binding BoundB, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>

UserControl的XAML代码如下:

<UserControl x:Class="Problem30TwoDoubles.TextBoxTwoDoubles"
    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:Problem30TwoDoubles"
    mc:Ignorable="d" Name="Parent"
    d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid>
            <TextBox Text="{Binding ElementName=Parent, Path=Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
        </Grid>
    </Grid>
</UserControl>

代码部分较长。它定义了三个依赖属性:"Text",这是输入的原始文本。还定义了"NumberA"和"NumberB"依赖属性。
输入的文本被分割成两个数字。每个数字都经过验证并可能经过增强处理。以下是代码:

public partial class TextBoxTwoDoubles : UserControl
{
    public TextBoxTwoDoubles()
    {
        InitializeComponent();
    }
    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set
        {
            SetValue(TextProperty, value);
            SetValue(TextProperty, value);
        }
    }
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(TextBoxTwoDoubles),
          new PropertyMetadata(string.Empty, new PropertyChangedCallback(TextPropertyChanged)));

    public double NumberA
    {
        get { return (double)GetValue(NumberAProperty); }
        set
        {
            SetValue(NumberAProperty, value);
        }
    }
    public static readonly DependencyProperty NumberAProperty =
       DependencyProperty.Register("NumberA", typeof(double), typeof(TextBoxTwoDoubles),
         new PropertyMetadata(double.NaN, new PropertyChangedCallback(NumberAPropertyChanged));

    public double NumberB
    {
        get { return (double)GetValue(NumberBProperty); }
        set
        {
            SetValue(NumberBProperty, value);
        }
    }
    public static readonly DependencyProperty NumberBProperty =
       DependencyProperty.Register("NumberB", typeof(double), typeof(TextBoxTwoDoubles),
         new PropertyMetadata(double.NaN, new PropertyChangedCallback(NumberBPropertyChanged));

    private static void TextPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        TextBoxTwoDoubles ours = (TextBoxTwoDoubles)obj;
        if (e.NewValue == e.OldValue) return;
        string[] splitted = (e.NewValue as string).Split();
        if (splitted.Length != 2)
        {
            ours.SetValue(TextProperty, ours.NumberA.ToString() + " " + ours.NumberB.ToString());
            return;
        }
        string stringA = splitted[0];
        string stringB = splitted[1];
        double _tempA;
        double _tempB;
        string examinedA = NormalizeToDouble(stringA);
        string examinedB = NormalizeToDouble(stringB);
        string toBeExamined = (string)e.NewValue;

        if (!(double.TryParse(examinedA, out _tempA) && double.TryParse(examinedB, out _tempB))
        {
            ours.SetValue(TextProperty, ours.NumberA.ToString() + " " + ours.NumberB.ToString());
        }
        else
        {
            ours.SetValue(NumberAProperty, _tempA);
            ours.SetValue(NumberBProperty, _tempB);
        }
    }
    private static string NormalizeToDouble(string text)
    {
        string toBeExamined = text;
        if (!toBeExamined.Contains("."))
        {
            toBeExamined += ".0";
        }
        if (toBeExamined.EndsWith("."))
        {
            toBeExamined += "0";
        }
        return toBeExamined;
    }
    private static void NumberAPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        TextBoxTwoDoubles ours = (TextBoxTwoDoubles)obj;
        if (e.NewValue == e.OldValue) return;
        ours.SetValue(TextProperty, ours.NumberA.ToString() + " " + ours.NumberB.ToString());
    }
    private static void NumberBPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        TextBoxTwoDoubles ours = (TextBoxTwoDoubles)obj;
        if (e.NewValue == e.OldValue) return;
        ours.SetValue(TextProperty, ours.NumberA.ToString() + " " + ours.NumberB.ToString());
    }
}

我是基于几周前完成的另一个解决方案构建的,该解决方案处理了单个双精度数的解析。如果您想首先看到一个更简单的解决方案,这可能会有所帮助。【原文链接](https://stackoverflow.com/questions/75460472/hi-i-want-validate-textbox-wpf-mvvm-pattern-to-allow-number-with-decimal-value/75464023#75464023)。

英文:

The proposed solution is based on a UserControl that is doing the work in its code behind.
Lets start with how we use the UserControl called "TextBoxTwoDoubles" :

<local:TextBoxTwoDoubles
NumberA="{Binding BoundA,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
NumberB="{Binding BoundB,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
/>

The UserControl XAML code is as following:

<UserControl x:Class="Problem30TwoDoubles.TextBoxTwoDoubles"
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:Problem30TwoDoubles"
mc:Ignorable="d"  Name="Parent"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid>
<TextBox Text="{Binding ElementName=Parent,Path=Text,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"   />
</Grid>
</Grid>
</UserControl>

The code behind is longer. It defines three Dependency Properties: "Text" which is the raw text entered. It defines also the "NumberA" and "NumberB" dependency Properties.
The entered text is splitted to 2 numbers . Each of the numbers goes thru validation and can go thru enhancements. This is the code:

public partial class TextBoxTwoDoubles : UserControl
{
public TextBoxTwoDoubles()
{
InitializeComponent();
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set
{
SetValue(TextProperty, value);
SetValue(TextProperty, value);
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(TextBoxTwoDoubles),
new PropertyMetadata(string.Empty, new PropertyChangedCallback(TextPropertyChanged)));
public double NumberA
{
get { return (double)GetValue(NumberAProperty); }
set
{
SetValue(NumberAProperty, value);
}
}
public static readonly DependencyProperty NumberAProperty =
DependencyProperty.Register("NumberA", typeof(double), typeof(TextBoxTwoDoubles),
new PropertyMetadata(double.NaN, new PropertyChangedCallback(NumberAPropertyChanged)));
public double NumberB
{
get { return (double)GetValue(NumberBProperty); }
set
{
SetValue(NumberBProperty, value);
}
}
public static readonly DependencyProperty NumberBProperty =
DependencyProperty.Register("NumberB", typeof(double), typeof(TextBoxTwoDoubles),
new PropertyMetadata(double.NaN, new PropertyChangedCallback(NumberBPropertyChanged)));
private static void TextPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBoxTwoDoubles ours = (TextBoxTwoDoubles)obj;
if (e.NewValue == e.OldValue) return;
string[] splitted = (e.NewValue as string).Split();
if (splitted.Length!= 2)
{
ours.SetValue(TextProperty, ours.NumberA.ToString() + " " + ours.NumberB.ToString());
return;
}
string stringA = splitted[0];
string stringB = splitted[1];
double _tempA;
double _tempB;
string examinedA = NormalizeToDouble(stringA);
string examinedB = NormalizeToDouble(stringB);
string toBexamined = (string)e.NewValue;
if (!(double.TryParse(examinedA, out _tempA) && double.TryParse(examinedB, out _tempB)) )
{
ours.SetValue(TextProperty, ours.NumberA.ToString() + " " + ours.NumberB.ToString());
}
else
{
ours.SetValue(NumberAProperty, _tempA);
ours.SetValue(NumberBProperty, _tempB);
}
}
private static  string NormalizeToDouble(string text)
{
string toBeExamined = text;
if (!toBeExamined.Contains("."))
{
toBeExamined += ".0";
}
if (toBeExamined.EndsWith("."))
{
toBeExamined += "0";
}
return toBeExamined;
}
private static void NumberAPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBoxTwoDoubles ours = (TextBoxTwoDoubles)obj;
if (e.NewValue == e.OldValue) return;
ours.SetValue(TextProperty, ours.NumberA.ToString() + " " + ours.NumberB.ToString());
}
private static void NumberBPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TextBoxTwoDoubles ours = (TextBoxTwoDoubles)obj;
if (e.NewValue == e.OldValue) return;
ours.SetValue(TextProperty, ours.NumberA.ToString()+ " " + ours.NumberB.ToString());
}
}

I have based it on another solution I have done few weeks ago that handled a single double parsing. This can be helpful if one wants to see a more simple solution first
https://stackoverflow.com/questions/75460472/hi-i-want-validate-textbox-wpf-mvvm-pattern-to-allow-number-with-decimal-value/75464023#75464023

答案2

得分: 0

以下是翻译好的部分:

根据问题更新,我正在添加一个解决方案,用于修复不工作的转换器。ConvertBack应该返回双精度值

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
    string[] splitValues = ((string)value).Split(';');
    double doubleA = 0.0;
    double doubleB = 0.0;
    if (splitValues.Length == 2)
    {
        double.TryParse(splitValues[0], out doubleA);
        double.TryParse(splitValues[1], out doubleB);
    }
    object[] doubles = { doubleA, doubleB };
    return doubles;
}
英文:

As of the question update I am adding a solution for the converter that is not working. The ConvertBack should return the values as doubles:

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
string[] splitValues = ((string)value).Split(';');
double doubleA=0.0;
double doubleB=0.0;
if (splitValues.Length == 2)
{
double.TryParse(splitValues[0], out doubleA);
double.TryParse(splitValues[1], out doubleB);
}
object[] doubles =  { doubleA, doubleB };
return doubles;
}

答案3

得分: 0

以下是您要翻译的内容:

Solution 1

Split the string in the binding source and convert it to double values.
This solution is best suited for data validation.

ViewData.cs

class ViewData : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler? PropertyChanged;

  private string interpolationBoundsText;
  public string InterpolationBoundsText
  {
    get => this.interpolationBoundsText;
    set 
    { 
      this.interpolationBoundsText = value;
      OnPropertyChanged();

      // Split the input and update the lower 
      // and upper bound properties
      OnInterpolationBoundsTextChanged();
    }
  }

  private double lowerInterpolationBound;
  public double LowerInterpolationBound
  {
    get => this.lowerInterpolationBound;
    set
    {
      this.lowerInterpolationBound = value;
      OnPropertyChanged();
    }
  }

  private double upperInterpolationBound;
  public double UpperInterpolationBound
  {
    get => this.upperInterpolationBound;
    set
    {
      this.upperInterpolationBound = value;
      OnPropertyChanged();
    }
  }

  private void OnInterpolationBoundsTextChanged()
  {
    string[] bounds = this.InterpolationBoundsText.Split(new[] {';', ',', ' ', '/', '-'}, StringSplitOptions.RemoveEmptyEntries);
    if (bounds.Length > 2)
    {
      throw new ArgumentException("Found more than two values.", nameof(this.InterpolationBoundsText));
    }

    // You should implement data validation at this point to give
    // the user error feedback to allow him to correct the input.
    if (double.TryParse(bounds.First(), out double lowerBoundValue))
    {
      this.LowerInterpolationBound = lowerBoundValue;
    }

    // You should implement data validation at this point to give
    // the user error feedback to allow him to correct the input.
    if (double.TryParse bounds.Last(), out double upperBoundValue))
    {
      this.LowerInterpolationBound = upperBoundValue;
    }
  }

  private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

MainWindow.xaml
The DataContext is expected to be of type ViewData.

<Window>
<ClearableTextBox Text="{Binding InterpolationBoundsText}" />
</Window>

Solution 2

Example shows how to use a MultiBinding to split the input using a converter.
The example is based on the above version of the ViewData class.

InpolationBoundsInputConverter.cs

class InpolationBoundsInputConverter : IMultiValueConverter
{
  public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) => values
    .Cast<double>()
    .Aggregate(
      string.Empty, 
      (newStringValue, doubleValue) => $"{newStringValue};{doubleValue}", 
      result => result.Trim(';'));

  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => ((string)value)
    .Split(new[] { ';', ',', ' ', '/', '-' }, StringSplitOptions.RemoveEmptyEntries)
    .Select(textValue => (object)double.Parse(textValue))
    .ToArray();
}

MainWindow.xaml.cs
The DataContext is expected to be of type ViewData.

<Window>
<ClearableTextBox>
<ClearableTextBox.Text>
<MultiBinding UpdateSourceTrigger="LostFocus">
<MultiBinding.Converter>
<InpolationBoundsInputConverter />
</MultiBinding.Converter>
<Binding Path="LowerInterpolationBound" />
<Binding Path="UpperInterpolationBound" />
</MultiBinding>
</ClearableTextBox.Text>
</ClearableTextBox>
</Window>
英文:

The first solution shows how to bind the TextBox to a string property and split the value in the data source (the ViewData class).
The second solution binds the TextBox directly to both properties of type double. A IMultivalueConverter imlementation will first split the values and then convert them from string to double.

It is recommended to implement data validation using the INotifyDataErrorInfo interface. This is pretty simple and allows you to give the user feedback when he enters invalid input (for example alphabetic input or invalid number of arguments or wrong separator). The error feedback usually is a red border around the input field and an error message that guides the user to fix the input.
You can follow this example to learn how to implement the interface: How to add validation to view model properties or how to implement INotifyDataErrorInfo.

Because of fragile nature of an expected text input that is subject to strict rules and constraints (e.g. input must be numeric, must use a particular set of delimiters, must contain only two values etc.), it is highly recommended to implement data validation.

In terms of data validation, the first solution is recommended (split and convert and validate values in the data source).

Solution 1

Split the string in the binding source and convert it to double values.
This solution is best suited for data validation.

ViewData.cs

class ViewData : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler? PropertyChanged;

  private string interpolationBoundsText;
  public string InterpolationBoundsText
  {
    get =&gt; this.interpolationBoundsText;
    set 
    { 
      this.interpolationBoundsText = value;
      OnPropertyChanged();

      // Split the input and update the lower 
      // and upper bound properties
      OnInterpolationBoundsTextChanged();
    }
  }

  private double lowerInterpolationBound;
  public double LowerInterpolationBound
  {
    get =&gt; this.lowerInterpolationBound;
    set
    {
      this.lowerInterpolationBound = value;
      OnPropertyChanged();
    }
  }

  private double upperInterpolationBound;
  public double UpperInterpolationBound
  {
    get =&gt; this.upperInterpolationBound;
    set
    {
      this.upperInterpolationBound = value;
      OnPropertyChanged();
    }
  }

  private void OnInterpolationBoundsTextChanged()
  {
    string[] bounds = this.InterpolationBoundsText.Split(new[] {&#39;;&#39;, &#39;,&#39;, &#39; &#39;, &#39;/&#39;, &#39;-&#39;}, StringSplitOptions.RemoveEmptyEntries);
    if (bounds.Length &gt; 2)
    {
      throw new ArgumentException(&quot;Found more than two values.&quot;, nameof(this.InterpolationBoundsText));
    }

    // You should implement data validation at this point to give
    // the user error feedback to allow him to correct the input.
    if (double.TryParse(bounds.First(), out double lowerBoundValue))
    {
      this.LowerInterpolationBound = lowerBoundValue;
    }

    // You should implement data validation at this point to give
    // the user error feedback to allow him to correct the input.
    if (double.TryParse(bounds.Last(), out double upperBoundValue))
    {
      this.LowerInterpolationBound = upperBoundValue;
    }
  }

  private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    =&gt; this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

MainWindow.xaml
The DataContext is expected to of type ViewData.

&lt;Window&gt;
&lt;ClearableTextBox Text=&quot;{Binding InterpolationBoundsText}&quot; /&gt;
&lt;/Window&gt;

Solution 2

Example shows how to use a MultiBinding to split the input using a converter.
The example is based on the above version of the ViewData class.

InpolationBoundsInputConverter.cs

class InpolationBoundsInputConverter : IMultiValueConverter
{
  public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =&gt; values
    .Cast&lt;double&gt;()
    .Aggregate(
      string.Empty, 
      (newStringValue, doubleValue) =&gt; $&quot;{newStringValue};{doubleValue}&quot;, 
      result =&gt; result.Trim(&#39;;&#39;));

  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =&gt; ((string)value)
    .Split(new[] { &#39;;&#39;, &#39;,&#39;, &#39; &#39;, &#39;/&#39;, &#39;-&#39; }, StringSplitOptions.RemoveEmptyEntries)
    .Select(textValue =&gt; (object)double.Parse(textValue))
    .ToArray();
}

MainWindow.xaml.cs
The DataContext is expected to be of type ViewData.

&lt;Window&gt;
&lt;ClearableTextBox&gt;
&lt;ClearableTextBox.Text&gt;
&lt;MultiBinding UpdateSourceTrigger=&quot;LostFocus&quot;&gt;
&lt;MultiBinding.Converter&gt;
&lt;InpolationBoundsInputConverter /&gt;
&lt;/MultiBinding.Converter&gt;
&lt;Binding Path=&quot;LowerInterpolationBound&quot; /&gt;
&lt;Binding Path=&quot;UpperInterpolationBound&quot; /&gt;
&lt;/MultiBinding&gt;
&lt;/ClearableTextBox.Text&gt;
&lt;/ClearableTextBox&gt;
&lt;/Window&gt;

huangapple
  • 本文由 发表于 2023年4月10日 22:36:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/75978029.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定