wpf 如何使用Converter从TextBox中读取由分隔符分隔的两个值,然后将它们绑定到两个不同的属性?

nxowjjhe  于 2023-04-22  发布在  其他
关注(0)|答案(3)|浏览(306)

我目前在一个插值项目工作。我需要用户输入插值边界(基本上,2双值)在一个文本框由一些分隔符分开。
在我的MainWindow.xaml.cs中,我创建了一个类ViewData,它的字段是用户界面中的控件。并将我的DataContext分配给它。像这样:

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

    }

特别地,这个类有两个double类型的字段:boundA和bounddB。我希望能够从TextBox获取用户输入,并将第一个值绑定到boundA,第二个绑定到bounddB。我的ViewData类:

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"; 
        }
    }
}
  • 更新 * 我试过使用IMultiValueConverter + MultiBinding,但我没能成功:(下面是我的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>

我的转换器:

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;
        }
    }
r6hnlfcb

r6hnlfcb1#

第一个解决方案展示了如何将TextBox绑定到一个字符串属性并拆分数据源(ViewData类)中的值。
第二种解决方案是将TextBox直接绑定到double类型的两个属性。IMultivalueConverter实现将首先拆分值,然后将它们从string转换为double
建议使用INotifyDataErrorInfo接口实现数据验证。这非常简单,当用户输入无效输入(例如字母输入或参数数量无效或错误分隔符)时,您可以向用户提供反馈。错误反馈通常是输入字段周围的红色边框和错误消息,指导用户修复输入。
您可以按照此示例学习如何实现接口:How to add validation to view model properties or how to implement INotifyDataErrorInfo
由于预期文本输入的脆弱性受到严格的规则和约束(例如,输入必须是数字,必须使用特定的分隔符集,必须仅包含两个值等),因此强烈建议实现数据验证。
在数据验证方面,建议使用第一种解决方案(拆分、转换和验证数据源中的值)。

方案一

拆分绑定源中的字符串并将其转换为双精度值。
此解决方案最适合数据验证。

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.xamlDataContext的类型应为ViewData

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

方案二

示例显示如何使用MultiBinding通过转换器拆分输入。
该示例基于ViewData类的上述版本。

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

DataContext的类型应为ViewData

<Window>
  <ClearableTextBox>
    <ClearableTextBox.Text>
      <MultiBinding UpdateSourceTrigger="LostFocus">
        <MultiBinding.Converter>
          <InpolationBoundsInputConverter />
        </MultiBinding.Converter>

        <Binding Path="LowerInterpolationBound" />
        <Binding Path="UpperInterpolationBound" />
      </MultiBinding>
    </ClearableTextBox.Text>
  </ClearableTextBox>
</Window>
qyzbxkaa

qyzbxkaa2#

建议的解决方案是基于一个UserControl,该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”依赖属性。输入的文本被拆分为2个数字。每个数字都经过验证并可以经过增强。这是代码:

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());
        }
    }

我已经基于另一个解决方案,我已经做了几个星期前,处理一个单一的双重解析。这可能是有帮助的,如果一个人想看到一个更简单的解决方案第一嗨,我想验证文本框WPF MVVM模式,以允许数字与十进制值

xbp102n0

xbp102n03#

在问题更新时,我为无法工作的转换器添加了一个解决方案。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;
        }

相关问题