Question

I'm working on WinRT with MvmmCross v3 framework and Windows.UI.Interactivity.

I want a TextBox with an EventTrigger on the event TextChanged which launch a Command. And also, I want in CommandParameter the text of the textBox.

So I have this code

   <i:EventTrigger EventName="TextChanged">
                    <i:InvokeCommandAction  Command="{Binding UpdateText}" CommandParameter="{Binding Text}"/>
                </i:EventTrigger>


public ICommand UpdateText
{
    get
    {
        return new MvxCommand<object>((textSearch) =>
            {
                // code...
            });
    }
}

But my textSearch parameter equals to "{Windows.UI.Xaml.Controls.TextChangedEventArgs}" with all of these properties NULL.

I Tried also to declare explicitly my ElementName in the binding like

CommandParameter="{Binding Path=Text, ElementName=tes}

But it failed too.

Thanks

Was it helpful?

Solution

Do you really need to handle TextChanged event? You could be notified of the changes by just binding to the Text property:

<TextBox Text="{Binding TextValue, Mode=TwoWay}" />

And then in the view model:

private string _textValue;
public string TextValue
{
    get
    {
        return _textValue;
    }
    set
    {
        if (_textValue == value)
            return;
        _textValue = value;
        OnTextChanged(value); // react to the changed value
    }
}

EDIT:

There are two things you need to be aware of, if you want to get to the Text value from inside your Command:

First, you need to fix the CommandParameter binding. By using {Binding Text} you are actually trying to bind to a property in your view model, i.e. you would first need to bind the TextBox.Text property to the same view model property. As you've said in the comment, that's no good for you because you need the info on every change and not only on lost focus, so the value you get is not up to date enough.

The right approach would therefore be your second attempt, i.e. binding directly to the TextBox using the ElementName syntax. Unfortunately triggers are not a part of the visual tree therefore they don't get access to the XAML name scope (you can read more about it in this blog post). You can work around that by using NameScopeBinding from MVVMHelpers.Metro package:

<Behaviors:NameScopeBinding x:Key="MyTextBox" 
                            Source="{Binding ElementName=MyTextBox}" />

Now you can make the ElementName binding work:

<i:InvokeCommandAction Command="{Binding UpdateText}" 
    CommandParameter="{Binding Source.Text, Source={StaticResource MyTextBox}}"/>

You still have the second problem. The Text value that you are binding to only updates on lost focus so you don't get the right value when handling TextChanged event. The solution is to bind to the TextBox itself:

<i:InvokeCommandAction Command="{Binding UpdateText}" 
    CommandParameter="{Binding Source, Source={StaticResource MyTextBox}}"/>

And then inside the command get the Text property directly from the TextBox:

private void OnUpdateText(object parameter)
{
    var value = ((TextBox) parameter).Text;
    // process the Text value as you need to.
}

EDIT 2:

To make the above code work with the view model being in a PCL, there a couple of approaches you could take.

The simplest hack would be to use reflection. Since it is available in PCL you could get to the Text property and read its value:

private void OnUpdateText(object parameter)
{
    var textProperty = textSearch.GetType().GetProperty("Text");
    var value = textProperty.GetValue(parameter, null) as string;
    // process the Text value as you need to.
}

A cleaner solution would be to put the WinRT specific code into a separate assembly abstracted via an interface. You would first create an interface in the PCL library:

public interface IUiService
{
    string GetTextBoxText(object textBox);
}

And modify view model to accept this interface in the constructor:

public ViewModel(IUiService uiService)
{
    _uiService = uiService;
}

In the command handler you would than use the method in the interface:

private void OnUpdateText(object parameter)
{
    var value = _uiService.GetTextBoxText(parameter);
    // process the Text value as you need to.
}

You would implement that interface in a WinRT library:

public class UiService : IUiService
{
    public string GetTextBoxText(object textBox)
    {
        var typedTextBox = textBox as TextBox;
        return typedTextBox.text;
    }
}

In the application you reference this library and pass the implementation to view model:

var viewModel = new ViewModel(new UiService);

My favorite approach is different: I'd create a Behavior exposing a Text property automatically updated every time TextChanged event is triggered:

public class TextChangedBehavior : Behavior<TextBox>
{
    public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
        "Text", 
        typeof(string), 
        typeof(TextChangedBehavior), 
        new PropertyMetadata(null));

    public string Text
    {
        get { return (string) GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }


    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.TextChanged += OnTextChanged;
        Text = AssociatedObject.Text;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.TextChanged -= OnTextChanged;
    }

    private void OnTextChanged(object sender, TextChangedEventArgs textChangedEventArgs)
    {
        Text = AssociatedObject.Text;
    }
}

Now I could bind a TextValue property to this behavior and react to its change in the property setter as already suggested at the very beginning of this long answer:

<TextBox>
    <i:Interaction.Behaviors>
        <b:TextChangedBehavior Text="{Binding TextValue, Mode=TwoWay}" />
    </i:Interaction.Behaviors>
</TextBox>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top