如何撤消由绑定引起的TextBox文本更改?

时间:2010-12-18 02:28:18

标签: wpf textbox wpf-controls binding

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

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

6 个答案:

答案 0 :(得分:6)

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

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

答案 1 :(得分:6)

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

  1. UpdateSourceTrigger绑定的TextBox.Text设置为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
      }
    }
    
  3. 我发现这个解决方案非常优雅,因为它也可以在DataTemplates中使用。例如,在我的情况下,我使用它来允许ListBox项目的就地编辑。

答案 2 :(得分:5)

直接分配给TextBox:

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

答案 3 :(得分:2)

我认为你必须考虑一些替代方法,比如

http://blog.notifychanged.com/2009/01/30/using-the-viewmodel-pattern-to-provide-undo-redo-in-wpf/

同样允许文本框处理undostack似乎有一些内存泄漏问题,请参阅此处

http://www.infosysblogs.com/microsoft/2008/03/wpf_textbox_memory_leak_issue_1.html

答案 4 :(得分:1)

TextBox会将更改应用于内部撤消堆栈,如果它们的应用方式似乎来自用户,如下所示:

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

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

答案 5 :(得分:0)

所以,我认为ViewModel Undo / Redo文章很好,但它与ViewModel模式一样多,因为它与如何编写自定义撤消/重做功能有关。另外,为了回应confusedGeek,我认为可能有一些示例可以撤消模型中的更改,而不仅仅是在您的单个控件中(例如,您有一个文本框和滑块都绑定到示例属性,您要撤消更改)无论采用哪种控制方式,我们都在谈论应用级别撤销而不是控制级别。

所以考虑到这一点,这里有一个简单的,如果不是有点像kludgey的例子,使用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)我没有将多个击键分组到一个撤销步骤中,这两个都是你想要的东西考虑一个真实的应用程序,但应该相对简单地实现自己。