WPF 文本框绑定与格式设置

发布于 2024-10-10 02:24:31 字数 1592 浏览 0 评论 0原文

我刚刚将我们的 wpf 应用程序从 3.5sp1 升级到 4.0。

下面的代码我们用来将文本框绑定到底层视图模型。文本框是可编辑的。

    <TextBox HorizontalContentAlignment="Right"
Text="{Binding Path=Price,   StringFormat={0:#,##0;(#,##0)},  Mode=TwoWay,  ValidatesOnDataErrors=True,  UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"/>

在 3.5sp1 中,格式化只会在最初发生。因此,当文本框加载并绑定到值 4000 时,格式会将其更改为 4,000。如果用户编辑该值,则不会发生格式化。

在 4.0 中,格式化随着值的变化而发生(即当用户输入新值时)。虽然理论上这听起来不错,但实际上这是一场灾难。光标到处都是。其无法使用。

现在,我们可以将 UpdateSourceTrigger 更改为“LostFocus”,但这会带来新问题,即在某些情况下无法验证数据。

有没有办法恢复旧的 3.5sp1 行为?

更新 1

使用 Converter 仍会产生相同的行为:

public class DecimalConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null)
            return ((decimal)value).ToString("#,##0;(#,##0)");

        return string.Empty;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}

以及修改后的 XAML:

<TextBox Text="{Binding Path=Price, Converter={StaticResource DecimalConverter}, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"/>

更新 2

与此类似 连接文章

I have just upgraded our wpf application from 3.5sp1 to 4.0.

The code below we use to bind the textbox to the underlying view model. The textbox is editable.

    <TextBox HorizontalContentAlignment="Right"
Text="{Binding Path=Price,   StringFormat={0:#,##0;(#,##0)},  Mode=TwoWay,  ValidatesOnDataErrors=True,  UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"/>

In 3.5sp1 the formatting would only occur initially. So when the textbox was loaded and bound to value 4000, the formatting would change it to 4,000. If user edited this value no formatting would occur.

In 4.0 the formatting occurs as the value changes (ie while user enters in new value). While in theory this sounds OK, in reality its a disaster. The cursor is all over the place. Its unusable.

Now, we could change the UpdateSourceTrigger to "LostFocus" but that introduces new problems with data not being validated in certain scenarios.

Is there a way to get the old 3.5sp1 behaviour back?

Update 1

Using Converter still procudes same behaviour:

public class DecimalConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null)
            return ((decimal)value).ToString("#,##0;(#,##0)");

        return string.Empty;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}

and the modified XAML:

<TextBox Text="{Binding Path=Price, Converter={StaticResource DecimalConverter}, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"/>

Update 2

Similar to this connect article.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

多像笑话 2024-10-17 02:24:31

作为更新,我采纳了 Jonathan 的建议并重新调整了 Binding 以使用 LostFocus 而不是 PropertyChanged(在适当的情况下 - 即还指定了 StringFormat 的地方)。

正如乔纳森所说,在某些情况下,您必须采用这种方法手动触发绑定刷新/验证。

如果有人有更好的方法,我很乐意看到。

As an update I took Jonathans suggestion and rejigged the Binding to use LostFocus instead of PropertyChanged (where appropriate - ie wherever StringFormat was also specified).

As Jonathan said, in some cases you have to trigger binding refresh / validation manually taking this approach.

If anyone has better approach, I would love to see it.

娇纵 2024-10-17 02:24:31

我对 LostFocus 解决方案并不满意,因此我决定编写一种方法来手动正确移动插入符。我已将其放在文件后面的代码中,并通过将其添加到 TextBox 上的 TextChanged 事件中,使其在每次文本更改时运行。

void moveCaret(object sender, TextChangedEventArgs args)
{
    TextBox tb = (TextBox) sender;
    if (args.Changes.Any())
    {
        var first = args.Changes.First();
        int offset = 1;
        if(first.AddedLength > 0)
        {
            if (tb.Text.Length > 4 && tb.Text.Length % 4 == 1)
                offset = 2;
            tb.CaretIndex = first.Offset + offset;
        }
        else
        {
            if (tb.CaretIndex > 0)
            {
                offset = 0;
                if (tb.Text.Length > 2 && (tb.Text.Length + 2) % 4 == 1)
                    offset = -1;
                tb.CaretIndex = first.Offset + offset;
            }
        } 
    }
    args.Handled = true;
}

只需将其添加到 TextChanged 事件中,如下所示:

MyTextBox.TextChanged += moveCaret;

我不是 100% 确定,但这似乎表现良好,尽管它不处理千位分隔符的删除。

编辑:我想出了如何处理千位分隔符。我在代码隐藏文件中创建了另一个方法,并将其放在 TextBox 上的 PreviewKeyDown 事件上。此方法检查文本框是否正在接收“删除”按钮输入的退格键,然后忽略它并移动插入符号。

private void handleThousandSeparator(object sender, KeyEventArgs e)
{
    var textBox = sender as TextBox;
    if (e.Key == Key.Back)
    {
        if (textBox.CaretIndex > 0)
        {
            if (textBox.Text[textBox.CaretIndex - 1] +"" == System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator)
            {
                if (textBox.Text[0] + "" == System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator)
                    return;
                textBox.CaretIndex = textBox.CaretIndex - 1;
                e.Handled = true;
            }
        }
    }
    if (e.Key == Key.Delete)
    {
        if (textBox.CaretIndex < textBox.Text.Length)
        {
            if (textBox.Text[textBox.CaretIndex] + "" == System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator)
            {
                if (textBox.Text[0] + "" == System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator)
                    return;
                textBox.CaretIndex = textBox.CaretIndex + 1;
                e.Handled = true;
            }
        }
    }
}     

请注意 TextBox 中第一个字符处的千位分隔符的特殊情况,它被删除而不是被跳过。理想情况下,千位分隔符应该永远不会出现,但 n0 数字格式化程序无法处理删除第一个千位分隔符之前的第一个数字的情况。

I wasn't satisfied with the LostFocus solution, so I decided to code a method that manually moves the caret correctly. I've put it in the code behind file and by adding it to the TextChanged event on the TextBox it get it to run every time the text changes.

void moveCaret(object sender, TextChangedEventArgs args)
{
    TextBox tb = (TextBox) sender;
    if (args.Changes.Any())
    {
        var first = args.Changes.First();
        int offset = 1;
        if(first.AddedLength > 0)
        {
            if (tb.Text.Length > 4 && tb.Text.Length % 4 == 1)
                offset = 2;
            tb.CaretIndex = first.Offset + offset;
        }
        else
        {
            if (tb.CaretIndex > 0)
            {
                offset = 0;
                if (tb.Text.Length > 2 && (tb.Text.Length + 2) % 4 == 1)
                    offset = -1;
                tb.CaretIndex = first.Offset + offset;
            }
        } 
    }
    args.Handled = true;
}

Just add this to the TextChanged event like so:

MyTextBox.TextChanged += moveCaret;

I'm not 100% sure, but this seems to behave well, though it doesn't handle deleting of the thousand separator.

EDIT: I figured out how to handle the thousand separator. I made another method in the code behind file, and put it on the PreviewKeyDown event on the TextBox. This method checks if the TextBox is receiving a Backspace of Delete button input, and just ignores it and moves the caret in stead.

private void handleThousandSeparator(object sender, KeyEventArgs e)
{
    var textBox = sender as TextBox;
    if (e.Key == Key.Back)
    {
        if (textBox.CaretIndex > 0)
        {
            if (textBox.Text[textBox.CaretIndex - 1] +"" == System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator)
            {
                if (textBox.Text[0] + "" == System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator)
                    return;
                textBox.CaretIndex = textBox.CaretIndex - 1;
                e.Handled = true;
            }
        }
    }
    if (e.Key == Key.Delete)
    {
        if (textBox.CaretIndex < textBox.Text.Length)
        {
            if (textBox.Text[textBox.CaretIndex] + "" == System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator)
            {
                if (textBox.Text[0] + "" == System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator)
                    return;
                textBox.CaretIndex = textBox.CaretIndex + 1;
                e.Handled = true;
            }
        }
    }
}     

Notice the special case for a thousand separator at the first char in the TextBox, where it is deleted in stead of skipped. A thousand separator should ideally never get to be there, but the n0 number formatter doesn't handle the case where you delete the first numbers before the first thousand separator.

晨曦慕雪 2024-10-17 02:24:31

您可以尝试删除 StringFormat={0:#,##0;(#,##0)} 并编写转换器进行格式化。

You can try to remove StringFormat={0:#,##0;(#,##0)} and write converter to do formating.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文