【问题标题】:How can I undo a TextBox's text changes caused by a binding?如何撤消由绑定引起的 TextBox 的文本更改?
【发布时间】:2011-05-27 10:50:46
【问题描述】:

我有一个TextBox 绑定了一个字符串,如果我现在手动编辑文本,我将能够通过TextBox.Undo() 撤消这些更改,但是如果我更改了字符串并且更新了 TextBox 的文本,我无法撤消这些更改,TextBox.CanUndo 属性将始终为 false
我想这可能与完全替换文本有关,而不是修改它。

关于如何让它发挥作用的任何想法?

【问题讨论】:

  • 您是否在绑定字符串中尝试过 Mode=TwoWay?
  • 没关系,双向绑定不起作用...

标签: wpf textbox wpf-controls binding


【解决方案1】:

好的,开始发表评论并意识到这是一个答案:)

TextBox.Undo() 旨在撤消用户与文本框的交互,而不是其绑定的属性中的值更改。文本框绑定的属性的更改只会更新 TextBox 的值,这与用户通过焦点/键盘进行编辑的更改不同。如果您需要撤消对绑定属性的更改,您可能需要研究将撤消/重做堆栈添加到您的应用程序。

【讨论】:

  • 我怀疑不会有一个简单的解决方案,然后再次尝试通过还原发生在控件而不是模型中的更改来尝试处理撤消操作并不是一个好主意。谢谢。
【解决方案2】:

所以,我认为 ViewModel 撤消/重做文章是一篇不错的文章,但它既是关于 ViewModel 模式的,也是关于如何编写自定义撤消/重做功能的。另外,为了回应confusedGeek,我认为可能存在一些示例,其中撤消模型中的更改,而不仅仅是在您的单个控件中是合适的(假设您有一个文本框和一个滑块都绑定到示例属性,您想要撤消更改不管是哪个控件实现的,所以我们谈论的是应用级撤消而不是控制级)。

因此,这里有一个简单的例子,如果不是有点笨拙的话,它使用 CommandBinding 和简单的撤消堆栈来精确地执行您所要求的操作:

public partial class MainWindow : Window
{
    public static readonly DependencyProperty MyStringProperty =
        DependencyProperty.Register("MyString", typeof(String), typeof(MainWindow), new UIPropertyMetadata(""));

    // The undo stack
    Stack<String> previousStrings = new Stack<String>();
    String cur = ""; // The current textbox value
    Boolean ignore = false; // flag to ignore our own "undo" changes

    public String MyString
    {
        get { return (String)GetValue(MyStringProperty); }
        set { SetValue(MyStringProperty, value); }
    }

    public MainWindow()
    {
        InitializeComponent();
        this.LayoutRoot.DataContext = this;

        // Using the TextChanged event to add things to our undo stack
        // This is a kludge, we should probably observe changes to the model, not the UI
        this.Txt.TextChanged += new TextChangedEventHandler(Txt_TextChanged);

        // Magic for listening to Ctrl+Z
        CommandBinding cb = new CommandBinding();
        cb.Command = ApplicationCommands.Undo;
        cb.CanExecute += delegate(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        };

        cb.Executed += delegate(object sender, ExecutedRoutedEventArgs e)
        {
            if (previousStrings.Count > 0)
            {
                ignore = true;
                this.Txt.Text = previousStrings.Pop();
                ignore = false;
            }

            e.Handled = true;
        };

        this.CommandBindings.Add(cb);
    }

    void Txt_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (!ignore)
        {
            previousStrings.Push(cur);
        }

        cur = this.Txt.Text;
    }

    private void SetStr_Click(object sender, RoutedEventArgs e)
    {
        this.MyString = "A Value";
    }
}

这里是 XAML:

<Window x:Class="TestUndoBinding.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<StackPanel  Name="LayoutRoot">
    <TextBox Name="Txt" Text="{Binding Path=MyString, Mode=TwoWay}" />
    <Button Name="SetStr" Click="SetStr_Click">Set to "A Value"</Button>
</StackPanel>
</Window>

在此示例中,该行为与典型的 TextBox 撤消行为略有不同,因为 1) 我忽略了选择,以及 2) 我没有将多个击键分组到单个撤消步骤中,这两者都是您想要的在实际应用中考虑,但自己实现应该相对简单。

【讨论】:

    【解决方案3】:

    直接赋值给TextBox:

    textBox.SelectAll();
    textBox.SelectedText = newText;
    

    【讨论】:

    • 我认为您误解了这个问题,这是关于撤消功能的,我知道如何覆盖现有文本。没有newText,我试图恢复一个应该存储在文本框历史记录中的旧值。
    • @H.B.这很好用!不会覆盖剪贴板,并允许您撤消不是由用户进行的文本分配!谢谢皮克斯! HB我会考虑用答案奖励皮克斯,因为这正是你在问题中所要求的。
    • @Darkhydro:不,不幸的是,问题是关于绑定引起的文本更改,即TextBox.Text 绑定的属性已更改,与TextBox 本身没有交互。
    • 这可以很容易地扩展为使用附加属性执行您想要的操作,因此您可以执行UserText.Value="{Binding Xyz}" 之类的操作,当文本更改时使用此答案中的方法更新文本,而不是使用文本直接属性。我知道这个答案已经过时了,但现在有人可能会对它感兴趣。
    【解决方案4】:

    如果更改看起来像是来自用户,TextBox 会将更改应用到内部撤消堆栈,如下所示:

            Clipboard.SetText("NewTextHere");
            TextBox.Paste();
    

    这是一个糟糕的解决方法,因为它会杀死用户在剪贴板上的所有内容(此处悲观地讨论了对剪贴板的恢复:How do I backup and restore the system clipboard in C#?),但我认为仍然值得发布。

    【讨论】:

    • 至少是一个有趣的想法。感谢您的贡献。
    【解决方案5】:

    我遇到了同样的问题(需要在 Enter 时接受输入并在 Escape 时恢复为原始值)并且能够以这种方式处理它:

    1. TextBox.Text 绑定的UpdateSourceTrigger 设置为Explicit
    2. 处理您的 TextBox 的 KeyDown 事件并将以下代码放入其中:

      if (e.Key == Key.Enter || e.Key == Key.Escape)
      {
        BindingExpression be = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
      
        if (e.Key == Key.Enter)
        {
          if (be != null) be.UpdateSource();
        }
        else if (e.Key == Key.Escape)
        {
          if (be != null) be.UpdateTarget(); //cancels newly supplied value and reverts to the original value
        }
      }
      

    我发现这个解决方案非常优雅,因为它也可以在 DataTemplates 中使用。例如,在我的例子中,我使用它来允许对 ListBox 项目进行就地编辑。

    【讨论】:

    • 这不包括需要立即值传播的情况。您可能还想避免失去焦点。不过,在某些情况下,这可能是一项不错的工作。
    猜你喜欢
    • 2018-03-21
    • 2015-09-25
    • 1970-01-01
    • 2011-05-06
    • 2012-09-06
    • 2011-01-15
    • 1970-01-01
    • 1970-01-01
    • 2017-02-15
    相关资源
    最近更新 更多