.NET 3.5 | WPF Textbox refuses to update itself while binded to a view model property
Asked Answered
G

1

0

Before I start explaining my issue, please note that my target framework is .NET 3.5.

I have a textbox whose text is bound to a viewmodel property. My requirement is that when user enters something(via keyboard as well as Mouse Paste) into the textbox, any junk characters inside it should be cleaned and the textbox should be updated with the replaced string[In the below example 's' to be replaced with 'h'].

XAMLCode:

 <Style x:Key="longTextField" TargetType="{x:Type TextBoxBase}">
            <Setter Property="SnapsToDevicePixels" Value="True"/>
            <Setter Property="OverridesDefaultStyle" Value="True"/>
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="AcceptsReturn" Value="True"/>
            <Setter Property="AllowDrop" Value="true"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TextBoxBase}">
                        <Border      
                      Name="Border"    
                      Padding="2"    
                      Background="Transparent"    
                      BorderBrush="LightGray"    
                      BorderThickness="1">
                            <ScrollViewer Margin="0" x:Name="PART_ContentHost"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Foreground" Value="Black"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
 <TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}"  MinLines="3"       TextWrapping="Wrap"
                 SpellCheck.IsEnabled="True" Style="{StaticResource longTextField}"></TextBox>

ViewModel property:

     private string _value;
            public string Value
            {
                get
                {
                    return _value;
                }
                set
                {
                    if (_value == value) 
                        return;

                    _value = value;
//replaces 's' with 'h' and should update the textbox.
                    _value = _value.Replace('s','h');
                    RaisePropertyChanged(() => Value);
                }
            }

The above is simply not working for me. The view model property setter is firing...the value is getting replaced..however the textbox is not getting updated. What is confusing is that this works perfectly on .Net4.0.

Do you know why this wont work and what is a potential solution to this problem, of course other than upgrading to .NET 4.0?

My requirement:

  • User can type as well as paste anything into a multilined textbox.

  • The text can contain junk which should be changed before it comes on to the textbox.

Thanks in advance, -Mike

Gilding answered 31/1, 2013 at 14:43 Comment(0)
C
6

I encountered a very similar problem where I wanted the two way binding and I was modifying the value in the ViewModel and expecting to see the update in the TextBox. I was able to get it resolved. Although I was using .NET 4.0, I basically had the same issue, so this may be worth trying out for your situation with 3.5 as well.

Short Answer:

What I encountered was a bug where the TextBox's displayed text was getting out of sync with the value of that TextBox's own Text property. Meleak's answer to a similar question clued me in to this and I was able to verify this with the debugger in Visual Studio 2010, as well as by employing Meleak's TextBlock technique.

I was able to resolve it by using explicit binding. This required handling the UpdateSource() and UpdateTarget() issues myself in code behind (or in a custom control code as I eventually did to make it easier to reuse).

Further Explanation:

Here's how I handled the explicit binding tasks. First, I had an event handler for the TextChanged event which updated the source of the binding:

// Push the text in the textbox to the bound property in the ViewModel
textBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();

Second, in I had an event handler for the TextBox's Loaded event. In that handler, I registered a handler for the PropertyChanged event of my ViewModel (the ViewModel was the "DataContext" here):

private void ExplicitBindingTextBox_Loaded(object sender, RoutedEventArgs e)
{
    TextBox textBox = sender as TextBox;

    if (textBox.DataContext as INotifyPropertyChanged == null)
        throw new InvalidOperationException("...");

    (textBox.DataContext as INotifyPropertyChanged).PropertyChanged +=
                  new PropertyChangedEventHandler(ViewModel_PropertyChanged);
}

Finally, in the PropertyChanged handler, I cause the TextBox to grab the value from the ViewModel (by initiating the UpdateTarget()). This makes the TextBox get the modified string from the ViewModel (in your case the one with replaced characters). In my case I also had to handle restoring the user's caret position after refreshing the text (from the UpdateTarget()). That part may or may not apply to your situation though.

    /// <summary>
    /// Update the textbox text with the value that is in the VM.
    /// </summary>
    void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        // This textbox only cares about the property it is bound to
        if (e.PropertyName != MyViewModel.ValueStrPropertyName)
            return;

        // "this" here refers to the actual textbox since I'm in a custom control
        //  that derives from TextBox
        BindingExpression bindingExp = this.GetBindingExpression(TextBox.TextProperty);
        // the version that the ViewModel has (a potentially modified version of the user's string)
        String viewModelValueStr;

        viewModelValueStr = (bindingExp.DataItem as MyViewModel).ValueStr;


        if (viewModelValueStr != this.Text)
        {
            // Store the user's old caret position (relative to the end of the str) so we can restore it
            //  after updating the text from the ViewModel's corresponding property.
            int oldCaretFromEnd = this.Text.Length - this.CaretIndex;

            // Make the TextBox's Text get the updated value from the ViewModel
            this.GetBindingExpression(TextBox.TextProperty).UpdateTarget();

            // Restore the user's caret index (relative to the end of the str)
            this.CaretIndex = this.Text.Length - oldCaretFromEnd;
        }

    }
Cnossus answered 31/1, 2013 at 15:51 Comment(1)
You may need to set your UpdateSourceTrigger to be Explicit, too. I certainly had to.Anselm

© 2022 - 2024 — McMap. All rights reserved.