Search code examples
c#wpfreactiveuireactivex

How to set a TextBox value with a button click in ReactiveUI


I am trying to get a handle on ReaxtiveX/ReaxtiveUI and tried to make a really simple program; a TextBox, a TextBlock, and a Button. I want the TextBox to have an initial value and whatever is typed in the TextBox to show in the TextBlock. When the button is clicked, the text in the TextBox will be changed, and therefore so will the text in the TextBlock. I got the TextBlock to update just fine, but can't get the button to work.

Here's my code

MainWindowViewModel.cs:

public class MainWindowViewModel : ReactiveObject
{
    //private string _inputText;
    //public string InputText
    //{
    //    get => _inputText;
    //    set => this.RaiseAndSetIfChanged(ref _inputText, value);
    //}

    private ObservableAsPropertyHelper<string> _inputText;
    public string InputText => _inputText.Value;

    private ObservableAsPropertyHelper<string> _copyText;
    public string CopyText => _copyText.Value;

    public ReactiveCommand<Unit, string> ChangeInputText { get; }
        
    public MainWindowViewModel ()
    {
        //InputText = "Starting Text";
        _inputText = ChangeInputText.ToProperty(this, nameof(InputText), "Starting Text");

        _copyText = this
            .WhenAnyValue(x => x.InputText)
            .ObserveOn(RxApp.TaskpoolScheduler)
            .Select(x => x.ToUpperInvariant())
            .DistinctUntilChanged()
            .WhereNotNull()
            .ObserveOn(RxApp.MainThreadScheduler)
            .ToProperty(this, x => x.CopyText);

            ChangeInputText = ReactiveCommand.Create<Unit, string>(_ => { return "Text Changed"; });
    }        
}

MainWindow.xaml.cs

public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
{
    public MainWindow ()
    {
        InitializeComponent();
        ViewModel = new MainWindowViewModel();
    
        this.WhenActivated(d =>
        {
            this.Bind(ViewModel, vm => vm.InputText, v => v.testTextBox.Text).DisposeWith(d);

            this.OneWayBind(ViewModel, vm => vm.CopyText, v => v.copyTextBlock.Text).DisposeWith(d);

            this.BindCommand(ViewModel, vm => vm.ChangeInputText, v => v.changeButton).DisposeWith(d);
        });
    }
}

MainWindow.xaml

<Border Padding="5">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <TextBox x:Name="testTextBox"
                Grid.Row="0"
                Grid.Column="0"
                Grid.ColumnSpan="3" />
        <Button x:Name="changeButton"
                Grid.Row="1"
                Grid.Column="1"
                Margin="5"
                Content="Change Text" />
        <Border BorderThickness="1"
                BorderBrush="Black"
                Grid.Row="2"
                Grid.Column="0"
                Grid.ColumnSpan="3">
            <TextBlock x:Name="copyTextBlock" />
        </Border>
    </Grid>
</Border>

I started with InputText as a property and set the initial value in the ViewModel ctor (before I tried to implement the button command). It worked, with the TextBox having an initial value, and its contents mirrored in the TextBlock. When I tried to implement the button command I changed InputText to an OAPH because, as far as I know, you cant use properties with ToProperty().

Now when I run the project I get this enter image description here

What am I doing wrong?

EDIT:

Here is the working ViewModel after Glenn Watson helped me.

public class MainWindowViewModel : ReactiveObject
{
    //Made Imput text a property instead of an OAPH
    private string _inputText;
    public string InputText
    {
        get => _inputText;
        set => this.RaiseAndSetIfChanged(ref _inputText, value);
    }

    private readonly ObservableAsPropertyHelper<string> _copyText;
    public string CopyText => _copyText.Value;

    public ReactiveCommand<Unit, string> ChangeInputText { get; }

    public MainWindowViewModel ()
    {
        InputText = "Starting Text";
        ChangeInputText = ReactiveCommand.Create<Unit, string>(_ => { return "Text Changed"; });//.BindTo(this, x => x.InputText);
        ChangeInputText.BindTo(this, x => x.InputText); //Added BindTo

    _copyText = this
        .WhenAnyValue(x => x.InputText)
        .ObserveOn(RxApp.TaskpoolScheduler)
        .Select(x => x.ToUpperInvariant())
        .DistinctUntilChanged()
        .WhereNotNull()
        .ObserveOn(RxApp.MainThreadScheduler)
        .ToProperty(this, x => x.CopyText);  
    }        
}

Solution

  • ChangeInputText RxCommand hasn't been created at the time you did ToProperty(). Move it underneath your ReactiveCommand.Create( call.