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

发布于 2024-10-08 08:56:56 字数 244 浏览 0 评论 0原文

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

关于如何让它发挥作用有什么想法吗?

I have a TextBox to which i bound a string, if i now edit the text manually i will be able to undo those changes via TextBox.Undo(), if however i change the string and the TextBox's text is updated, i cannot undo those changes and the TextBox.CanUndo property will always be false.
I suppose this might have to do with the complete replacement of the text rather than a modification of it.

Any ideas on how i can get this to work?

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

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

发布评论

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

评论(5

雨轻弹 2024-10-15 08:56:56

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

  1. Set UpdateSourceTrigger< /code> 将 TextBox.Text 绑定到 Explicit
  2. 处理 TextBox 的 KeyDown 事件并将以下代码放入其中:

    if (e.Key == Key.Enter || e.Key == Key.Escape)
    {
      BindingExpression = ((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(); //取消新提供的值并恢复为原始值
      }
    }
    

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

I was facing the same issue (needed to accept input upon Enter and revert to original value upon Escape) and was able to handle it this way:

  1. Set UpdateSourceTrigger of your TextBox.Text binding to Explicit.
  2. Handle KeyDown event of your TextBox and put the following code in there:

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

I found this solution to be very elegant because it can be used in DataTemplates too. For example in my case I used it to allow in-place editing of ListBox items.

永不分离 2024-10-15 08:56:56

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

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

OK, started to leave a comment and realized it was an answer :)

TextBox.Undo() is intended to undo a user's interaction with the text box not a value change in the property it's bound to. A change in the property the text box is bound to will just update the value of the TextBox, this is a different change than a user edit via focus/keyboard. If you need to Undo changes to your bound properties you probably need to investigate adding an Undo/Redo stack to your application.

风柔一江水 2024-10-15 08:56:56

直接分配给文本框:

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

Assign directly to the TextBox:

textBox.SelectAll();
textBox.SelectedText = newText;
孤云独去闲 2024-10-15 08:56:56

如果更改的应用方式看起来像是来自用户,则文本框会将更改应用到内部撤消堆栈,如下所示:

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

这是一个糟糕的解决方法,因为它会杀死用户在剪贴板上的所有内容(恢复这里悲观地讨论了其中的问题: 我该怎么办在 C# 中备份和恢复系统剪贴板?),但我认为尽管如此,还是值得发布的。

The TextBox will apply the changes to the internal undo stack if they are applied in such a way that they appear to have come from the user, like so:

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

It's a terrible workaround, as it kills whatever the user has on the clipboard (the restoring of which is pessimistically discussed here: How do I backup and restore the system clipboard in C#?) but I thought it might be worth having posted nonetheless.

后知后觉 2024-10-15 08:56:56

因此,我认为 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)我没有将多个击键分组为单个撤消步骤,这两件事都是您在实际应用程序中需要考虑的事情,但自己实现起来应该相对简单。

So, I think the ViewModel Undo/Redo article is a good one, but it's as much as about the ViewModel pattern as it is about how to write custom Undo/Redo functionality. Also, in response to confusedGeek, I think there could be examples where undoing changes in your model, not just in your individual controls is appropriate (say you had a textbox and a slider both bound to the sample property, you want to undo a change regardless of which control made it, so we're talking about app level undo instead of control level).

So given that, here is a simple, if not somewhat kludgey example of doing precisely what you ask using a CommandBinding and a simplistic undo stack:

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

And here is the 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>

In this example the behavior is slightly different than typical TextBox undo behavior because 1) I'm ignoring selection, and 2) I'm not grouping multiple keystrokes into a single undo step, both of which are things you would want to consider in a real app, but should be relatively straightforward to implement yourself.

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