Sun
Sun

Reputation: 4718

Windows 8 XAML Databinding on updating on text changed

I have a windows 8 XAML/C# application using the MVVM pattern.

All my textboxes on the form have their text properties bound to properties on my MVVM class.

So, one of my textboxes looks like this:

<TextBox x:Name="textAddressLine1" Text="{Binding AddressLine1, Mode=TwoWay}"/>

And that property on the MVVM class looks like this:

    private string addressLine1;

    public string AddressLine1
    {
        get { return addressLine1; }
        set
        {
            if (addressLine1 == value)
            {
                return;
            }

            addressLine1 = value;
            RaisePropertyChanged("AddressLine1");
        }
    }

As I type into my textbox the MVVM class isn't updated. It only gets updated once the focus moves to a different control.

How can I update the MVVM class property when the text changes on my textbox?

Thanks in advance

Upvotes: 1

Views: 3489

Answers (3)

Sia
Sia

Reputation: 24

I had the same issue, I found here: https://stackoverflow.com/a/11676076/4551080

<TextBox Text="{Binding Path=EmailAddress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

So make sure to set

UpdateSourceTrigger=PropertyChanged
Default Value is LostFocus

Upvotes: 1

Davut G&#252;rb&#252;z
Davut G&#252;rb&#252;z

Reputation: 5716

Use explicit binding for textAddressLine1

<TextBox x:Name="textAddressLine1" Text="{Binding AddressLine1,UpdateSourceTrigger=Explicit, Mode=TwoWay}" TextChanged="textAddressLine1_Changed"/>


private void textAddressLine1_Changed(object sender, RoutedEventArgs e)
{  
BindingExpression be = textAddressLine1.GetBindingExpression(TextBox.TextProperty);
be.UpdateTarget();
}

I didn't test the code but should work.

EDIT: I see it UpdateSourceTrigger is not exist for environtment

You can create a your viewmodel as instance and give it as datacontext by the way you can easily perform your viewmodel from your code-behind. For this type cases it saves the day!

 public MyClassViewModel ViewModel {get;set} 
 ctor()
 {
   this.ViewModel=new MyClassViewModel();
   this.DataContext=this.ViewModel;
   InitializeComponets();
 }

 private void textAddressLine1_Changed(object sender, RoutedEventArgs e)
 {  
   this.ViewModel.AddressLine1=textAddressLine1.Text;
 }

I think you will not need two way by this way. OneWay is ok. Because you explicitly change VM.

Note:I coded on SO, didn't test again.Hope helps!

Upvotes: 1

Erti-Chris Eelmaa
Erti-Chris Eelmaa

Reputation: 26268

Use this workaround:

public class ExtendedTextBox : TextBox
    {
        public static readonly DependencyProperty CustomActionProperty =
            DependencyProperty.Register(
            "CustomAction",
            typeof(Action<string>),
            typeof(ExtendedTextBox),
            new PropertyMetadata(null, OnPropertyChanged));

        public Action<string> CustomAction 
        {
            get
            {
                return (Action<string>)GetValue(CustomActionProperty);
            }
            set
            {
                SetValue(CustomActionProperty, value);
            }
        }

        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if(e.NewValue != null)
                (d as ExtendedTextBox).TextChanged += ExtendedTextBox_TextChanged;
            else
                (d as ExtendedTextBox).TextChanged -= ExtendedTextBox_TextChanged;
        }

        async static void ExtendedTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {            
            await CoreWindow.GetForCurrentThread().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => (sender as ExtendedTextBox).CustomAction((sender as ExtendedTextBox).Text));
        }        
    }

IN your model:

public Action<string> UpdateBindedViewModelProperty
        {
            get { return new Action<string>((value) => NewLabelName = value); }
        }

and view:

<plmrfc:extendedtextbox customaction="{Binding UpdateBindedViewModelProperty, Mode=OneTime}" text="{Binding Path=NewLabelName, Mode=TwoWay}" width="200" x:name="Label_TextBox"></plmrfc:extendedtextbox>

There is also another way, which does not involve subclassing TextBox. Maybe you like this one more:

using System.Reflection;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace Flexman
{
    public class TextBoxUpdateSourceBehaviour
    {
        private static PropertyInfo _boundProperty;

        public static readonly DependencyProperty BindingSourceProperty =
            DependencyProperty.RegisterAttached(
            "BindingSource", 
            typeof(string), 
            typeof(TextBoxUpdateSourceBehaviour),
            new PropertyMetadata(default(string), OnBindingChanged));

        public static void SetBindingSource(TextBox element, string value)
        {
            element.SetValue(BindingSourceProperty, value);
        }

        public static string GetBindingSource(TextBox element)
        {
            return (string)element.GetValue(BindingSourceProperty);
        }

        private static void OnBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var txt = d as TextBox;
            if (txt == null)
                return;

            txt.Loaded += OnLoaded;
            txt.TextChanged += OnTextChanged;
        }

        static void OnLoaded(object sender, RoutedEventArgs e)
        {
            var txt = sender as TextBox;
            if (txt == null)
                return;

            // Reflect the datacontext of the textbox to find the field to bind to.
            var dataContextType = txt.DataContext.GetType();
            _boundProperty = dataContextType.GetRuntimeProperty(GetBindingSource(txt));

            // If you want the behaviour to handle your binding as well, uncomment the following.
            //var binding = new Binding();
            //binding.Mode = BindingMode.TwoWay;
            //binding.Path = new PropertyPath(GetBindingSource(txt));
            //binding.Source = txt.DataContext;
            //BindingOperations.SetBinding(txt, TextBox.TextProperty, binding);
        }

        static void OnTextChanged(object sender, TextChangedEventArgs e)
        {
            var txt = sender as TextBox;
            if (txt == null)
                return;

            if (_boundProperty.GetValue(txt.DataContext).Equals(txt.Text)) return;
            _boundProperty.SetValue(txt.DataContext, txt.Text);
        }
    }
}

and view

<TextBox Text="{Binding Username}" Flexman:TextBoxUpdateSourceBehaviour.BindingSource="Username" />

This is the prettiest solution I know of. Others will be "non-generic" hacks. Good luck. I do agree that it's major downgrade from silverlight/WPF but hey, there are a lot of more horrible things in WinRT that are missing in WPF :)

Upvotes: 0

Related Questions