Eric B Eric B - 2 months ago 6
C# Question

Attempting to bind textbox with viewmodel but no update occuring

I've set up a small test project and created a login page that takes a username and a password. I'm using a MVVM approach and the Prism framework. I was following along with this video (https://www.youtube.com/watch?v=ZfBy2nfykqY).

Here is the xaml:

<Page x:Class="Control_Center.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Control_Center.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel VerticalAlignment="Center"
HorizontalAlignment="Center"
Grid.Row="0">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBlock Name="LoginResponse"
Text="{Binding LoginStatus}" />
</StackPanel>
</StackPanel>
<StackPanel VerticalAlignment="Center"
HorizontalAlignment="Center"
Grid.Row="1">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center">
<TextBox Name="UsernameBox"
Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="False"
PlaceholderText="Username..."
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="14"
Width="200"
Height="25" />
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center">
<PasswordBox Name="PasswordBox"
PasswordRevealMode="Peek"
PlaceholderText="Password..."
Width="200"
Height="25"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontSize="14"
PasswordChanged="PasswordBox_PasswordChanged" />
</StackPanel>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Name="LoginButton"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0,10,0,0"
FontSize="12"
Content="Login"
Command="{Binding LoginCommand}" />
<Button Name="CancelButton"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="10,10,0,0"
FontSize="12"
Content="Cancel" />

</StackPanel>
</StackPanel>
</Grid>
</Page>


And the viewModel (Note: BindableBase is part of Prism: https://github.com/PrismLibrary/Prism/blob/master/Source/Prism/Mvvm/BindableBase.cs):

using System;
using System.Diagnostics;
using System.Windows.Input;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Prism.Commands;
using Prism.Mvvm;

namespace EJD_Control_Center.ViewModels
{
class ViewLoginViewModel : BindableBase
{
private string _username;

public string Username
{
get { return _username; }
set { SetProperty(ref _username, value); }
}

private string _password;

public string Password
{
get { return _password; }
set { SetProperty(ref _password, value); }
}

private string _loginStatus;

public string LoginStatus
{
get { return _loginStatus; }
set { SetProperty(ref _loginStatus, value); }
}

public DelegateCommand LoginCommand { get; set; }

public ViewLoginViewModel()
{
LoginCommand = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => Username).ObservesProperty(() => Password);
}

private bool CanExecute()
{
Debug.WriteLine($"Username: {Username}, password: {Password}");
Debug.WriteLine("Username is not null or whitespace? " + !string.IsNullOrWhiteSpace(Username) + ", and password: " + !string.IsNullOrWhiteSpace(Password));
return !string.IsNullOrWhiteSpace(Username) && !string.IsNullOrWhiteSpace(Password);
}

private void Execute()
{
LoginStatus = $"Login Successful! Username: {Username} Password: {Password}";
//actually return the status of the login request, but we don't have this yet. then switch screens.
}
}
}


And for completion, the codebehind:

using Windows.Foundation;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Control_Center.ViewModels;

namespace Control_Center.Views
{
/// <summary>
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();

ApplicationView.PreferredLaunchViewSize = new Size(500, 320);
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;

DataContext = new ViewLoginViewModel();
}

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
var vm = (ViewLoginViewModel) DataContext;
vm.Password = PasswordBox.Password;
}
}
}


Now before anyone starts, I know this is not a secure way to handle passing the password. That's not the point of this question.

The problem is when I bind the username textbox to the Username variable in the ViewModel it doesn't seem to be updating that String. If I set the username in the ViewModel to something, say "test", it displays it in the textbox but no updates to that textbox changes the Username variable in the ViewModel.

The output I get when modifying those two boxes is:

Username: , password: d
Username is not null or whitespace? False, and password: True


Even when the username textbox has something in it. The only reason those debug lines appear at all is the way I'm handling the password is calling canExecute, but the way I'm handling the username doesn't even though as far as I understand it it should.

Why is the
"{Binding Username, UpdateSourceTrigger=PropertyChanged}"
not sending the username textbox text to the username variable in the ViewModel?

Answer

It is because the default Mode setting for Binding is OneWay - so update from code to layout works fine, but from layout to code won't. Set Mode=TwoWay to have it working both ways.

"{Binding Username, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"