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

Wpf 如何撤消文本框';s由绑定引起的文本更改?,wpf,textbox,wpf-controls,binding,Wpf,Textbox,Wpf Controls,Binding,我有一个绑定了字符串的TextBox,如果我现在手动编辑文本,我将能够通过TextBox.undo()撤消这些更改,但是如果我更改字符串并且文本框的文本被更新,我无法撤消这些更改,TextBox.CanUndo属性将始终是false 我想这可能与文本的完全替换有关,而不是对其进行修改 有什么想法可以让这个工作正常进行吗?好的,开始留言,意识到这是一个答案:) TextBox.Undo()用于撤消用户与文本框的交互,而不是其绑定属性中的值更改。文本框绑定属性的更改只会更新文本框的值,这与用户通过焦

我有一个绑定了字符串的
TextBox
,如果我现在手动编辑文本,我将能够通过
TextBox.undo()
撤消这些更改,但是如果我更改字符串并且文本框的文本被更新,我无法撤消这些更改,
TextBox.CanUndo
属性将始终是
false

我想这可能与文本的完全替换有关,而不是对其进行修改


有什么想法可以让这个工作正常进行吗?

好的,开始留言,意识到这是一个答案:)


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

因此,我认为ViewModel撤消/重做文章是一篇不错的文章,但它不仅涉及ViewModel模式,还涉及如何编写自定义撤消/重做功能。另外,作为对confusedGeek的回应,我认为可能有一些例子表明,撤销模型中的更改,而不仅仅是撤销单个控件中的更改是合适的(假设您有一个文本框和一个滑块都绑定到sample属性,您希望撤消一个更改,而不管是哪个控件做的,所以我们讨论的是应用程序级撤消,而不是控件级撤消)

因此,这里有一个简单的例子,如果不是有点笨拙的话,就是使用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";
    }
}
公共部分类主窗口:窗口
{
公共静态只读从属属性MyStringProperty=
Register(“MyString”、typeof(字符串)、typeof(主窗口)、新UIPropertyMetadata(“”);
//撤消堆栈
Stack previousStrings=新堆栈();
字符串cur=”“;//当前文本框值
Boolean ignore=false;//忽略我们自己的“撤消”更改的标志
公共字符串MyString
{
获取{return(String)GetValue(MyStringProperty);}
set{SetValue(MyStringProperty,value);}
}
公共主窗口()
{
初始化组件();
this.LayoutRoot.DataContext=this;
//使用TextChanged事件向撤消堆栈添加内容
//这是一个难题,我们可能应该观察模型的变化,而不是UI的变化
this.Txt.TextChanged+=新的textchangedventhandler(Txt\u TextChanged);
//聆听Ctrl+Z的魔力
CommandBinding cb=新CommandBinding();
cb.Command=ApplicationCommands.Undo;
cb.CanExecute+=委托(对象发送方,CanExecuteRouteEventArgs e)
{
e、 CanExecute=true;
};
cb.Executed+=委托(对象发送方,ExecutedRoutedEventArgs e)
{
如果(previousStrings.Count>0)
{
忽略=真;
this.Txt.Text=previousStrings.Pop();
忽略=错误;
}
e、 已处理=正确;
};
this.CommandBindings.Add(cb);
}
void Txt_TextChanged(对象发送者,textchangedventargs e)
{
如果(!忽略)
{
上一个字符串。推(cur);
}
cur=this.Txt.Text;
}
私有无效设置tru_单击(对象发送器,路由目标e)
{
this.MyString=“A值”;
}
}
下面是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>

设置为“A值”

在此示例中,行为与典型的文本框撤消行为略有不同,因为1)我忽略了选择,而2)我没有将多个击键分组到一个撤销步骤中,这两个都是在实际应用程序中要考虑的事情。但是应该相对简单地自己实现。

直接分配给文本框:

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

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

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

这是一个糟糕的解决方法,因为它会杀死用户在剪贴板上的任何东西(这里悲观地讨论了恢复:),但我认为它可能值得发布。我也面临同样的问题(需要在输入时接受输入,在转义时恢复到原始值)并且能够这样处理:

  • 设置
    TextBox的
    UpdateSourceTrigger
    。Text
    绑定到
    Explicit
  • 处理文本框的
    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
      }
    }
    

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

    您在绑定字符串中尝试过Mode=TwoWay吗?没关系,双向绑定不起作用……我怀疑没有简单的解决方案,然后再次强调,尝试通过还原控件中发生的更改来处理撤消并不是一个好主意,而不是模型。谢谢。我想你误解了这个问题,这是关于撤销功能的,我知道如何覆盖现有文本。没有
    newText
    ,我正在尝试还原一个本应存储在文本框历史记录中的旧值。@H.B.这非常有效!不会覆盖剪贴板,并允许您撤消非用户指定的文本!谢谢皮克斯!H.B.,我会考虑奖励Pixar的答案,因为这完全符合你在这个问题中所要求的。“DalkHyoDy:不,不幸的是,问题是关于绑定导致的文本更改,即绑定到的<代码> Text Box。与
    TextBox
    本身没有交互。可以轻松地扩展它,以便使用附加属性执行所需操作,因此