How to update UI in MVVMLight RelayCommand scenario? - c#

Here is a simple screen with one textblock which is "" initially, a button called "Set Text" which sets the text to the textblock and another button called "Clear text" which always clears the text in the textblock. This is how the XAML looks like.
<StackPanel>
<TextBlock Text="{Binding DisplayText, Mode=TwoWay}"></TextBlock>
<Button Content="Set Text" Command="{Binding SetTextCommand}"></Button>
<Button Content="Clear Text" Command="{Binding CancelCommand}"
IsEnabled="{Binding CanCancel, Mode=TwoWay}"/>
</StackPanel>
Here is my ViewModel code.
public class Page1VM : ViewModelBase
{
public RelayCommand SetTextCommand { get; private set; }
public RelayCommand CancelCommand { get; private set; }
public Page1VM()
{
SetTextCommand = new RelayCommand(HandleSetText, CanExecute);
CancelCommand = new RelayCommand(HandleClearButtonClick, CanExecuteCancel);
}
private void HandleSetText(string number)
{
DisplayText = number;
}
private string _displayText="";
public string DisplayText
{
get { return _displayText; }
set
{
_displayText = value;
RaisePropertyChanged("DisplayText");
RaisePropertyChanged("CanCancel");
}
}
private bool _canCancel;
public bool CanCancel
{
get
{
if (DisplayText == "")
{
return false;
}
else
{
return true;
}
}
set
{
_canCancel = value;
RaisePropertyChanged("CanCancel");
}
}
private bool CanExecute()
{
return true;
}
private bool CanExecuteCancel()
{
if (DisplayText == "")
{
return false;
}
else
{
return true;
}
}
private void HandleClearButtonClick()
{
DisplayText = "";
}
private void HandleSetText()
{
DisplayText = "Hello";
}
}
The problem : When the page is loaded, the "Clear text" button is disabled which is expected and works fine as intended.
When i click on "Set Text", i set a text to a textblock by setting a text value to property named DisplayText and also call RaisePropertyChanged("CanCancel"); but even after that my "Clear Text" button is not enabled. What can be the reason behind it ? My textblock shows the text value but the "clear text" button is still not enabled.

There's a bit mixing up going on in your example, as far as I can tell: You basically don't use the built-in 'CanExecute' mechanism of 'RelayCommand', but rebuild it yourself while still defining the CanExecute method of the 'RealyCommand'. The idea of 'CanExecute' is to automatically disbale controls whose command can't execute, so you don't need to do it manually. Returning 'true' in an 'CanExecute' method doesn't really make sense, as you don't necessarily need to have a CanExecute delegate in your RelayCommand (... = new RelayCommand(ExecuteCommand); is fine). Your scenario doesn't work because you're not calling 'RaisCanExecuteChanged()' on 'CancelCommand'.
Try the following implementation, I've removed the redundancies and inserted the missing 'RaiseCanExecuteChanged()'. See the comments for explanations:
<StackPanel>
<TextBlock Text="{Binding DisplayText, Mode=TwoWay}"></TextBlock>
<Button Content="Set Text" Command="{Binding SetTextCommand}"></Button>
<Button Content="Clear Text" Command="{Binding CancelCommand}" />
</StackPanel>
And use this simplified ViewModel:
public class Page1VM : ViewModelBase
{
public RelayCommand SetTextCommand { get; private set; }
public RelayCommand CancelCommand { get; private set; }
public Page1VM()
{
SetTextCommand = new RelayCommand(ExecuteSetText);
CancelCommand = new RelayCommand(ExecuteCancel, CanExecuteCancel);
}
private string _displayText="";
public string DisplayText
{
get { return _displayText; }
set
{
_displayText = value;
RaisePropertyChanged("DisplayText");
RaisePropertyChanged("CanCancel");
// Raise the CanExecuteChanged event of CancelCommand
// This makes the UI reevaluate the CanExecuteCancel
// Set a breakpoint in CanExecuteCancel method to make
// sure it is hit when changing the text
CancelCommand.RaiseCanExecuteChanged();
}
}
private bool CanExecuteCancel()
{
// You can simplify the statement like this:
return DisplayText != "";
}
private void ExecuteCancel()
{
DisplayText = "";
}
private void ExecuteSetText()
{
DisplayText = "Hello";
}
}

Related

Navigation button dependent on conditions from other ViewModel

This website has greatly benefited me, but now I have to ask a question of my own.
I am new to C# and MVVM applications..
Now I am building an app with different views, one view is the navigation view which should show other views depending on the navigation buttons. These buttons depend on the entered value in a view.
Example:
There is a navigationView containing a ContentControl and two buttons (nextStep and previousStep). I have put several textboxes in a view (parameterView) and associated model (parameterViewModel) which is displayed in the ContentControl. When all parameters are entered, the user may, by means of a button (nextStep mentioned before), go to the next step/view (checkDataView).
Now the button (in navigationView) must therefore be visible when all parameters are filled in parameterView, and hidden when one parameter is not filled in. The nextStep button should activate another page in the ContentControl.
I can navigate with checkboxes or radio buttons, but only without dependence on values ​​in another viewModel.
What should I do to get the dependency of parameters in another viewModel?
My NavigationView ContentControl and buttons are defined as:
<ContentControl Grid.Row="1"
Grid.ColumnSpan="5"
Content="{Binding CurrentItemView}" />
<Button Grid.Column="0"
Grid.Row="2"
Content="Next Step"
Style="{StaticResource SubMenuButton}"
Visibility="{Binding PreviousStepCommandVisibility}"
Command="{Binding PreviousStepCommand}"/>
<Button Grid.Column="3"
Grid.Row="2"
Content="Previous Step"
Style="{StaticResource SubMenuButton}"
Visibility="{Binding NextStepCommandVisibility}"
Command="{Binding NextStepCommand}"/>
My ViewModel of above View:
namespace SomeApp.MVVM.ViewModel
{
class GenerateMenuViewModel : BaseViewModel
{
public RelayCommand PreviousStepCommand { get; set; }
public RelayCommand NextStepCommand { get; set; }
private Visibility _previousStepCommandVisibility;
public Visibility PreviousStepCommandVisibility
{
get { return _previousStepCommandVisibility; }
set { _previousStepCommandVisibility = value; }
}
private Visibility _nextStepCommandVisibility;
public Visibility NextStepCommandVisibility
{
get { return _nextStepCommandVisibility; }
set { _nextStepCommandVisibility = value; }
}
public SomethingViewModel SomethingVM { get; set; }
private object _currentItemView;
public object CurrentItemView
{
get { return _currentItemView; }
set
{
_currentItemView = value;
OnPropertyChanged();
}
}
public GenerateMenuViewModel()
{
SomethingVM = new SomethingViewModel();
CurrentItemView = SomethingVM;
}
}
}
TextBoxes in View2, which values give dependence to the navigation buttons, are defined as:
<TextBox Text="{Binding Paramater1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Paramater2, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Paramater3, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The ViewModel which belongs to the above View:
namespace SomeApp.MVVM.ViewModel
{
class View1ViewModel : BaseViewModel
{
public View1ViewModel()
{
}
private string _parameter1;
public string Parameter1
{
get { return _parameter1; }
set { _parameter1 = value; OnPropertyChanged(); }
}
private string _parameter2;
public string Parameter2
{
get { return _parameter2; }
set { _parameter2 = value; OnPropertyChanged(); }
}
private string _parameter3;
public string Parameter3
{
get { return _parameter3; }
set { _parameter3 = value; OnPropertyChanged(); }
}
}
}
The simple way is to add a property in viewModel to indicate that all the parameters are validated and succeeded.
class View1
{
public bool Isvalid { get => !validationResults.Values.Contains(false); }
Dictionary<string, bool> validationResults = new Dictionary<string, bool>
{
{ nameof(Val1), false }
};
string val1 = "";
public string Val1
{
get => val1; set
{
val1 = value;
OnPropertyChanged();
validationResults[nameof(Val1)] = !string.IsNullOrEmpty(value); //Validation goes here
}
}

How to use View with the same functionality multiple times in other Views?

I am not sure if this is the correct question to ask but I don't know how else I could ask it.
In my project I have a LogInViewModel.cs
class LogInViewModel : BaseObservableObject
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; OnPropertyChanged("Text"); }
}
public LogInViewModel()
{
}
}
a MenuViewModel.cs
class MenuViewModel : BaseObservableObject
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; OnPropertyChanged("Text"); }
}
public LogInViewModel()
{
}
}
the Views for both LogInView.xaml and MenuView.xaml are
<StackPanel>
<TextBox Text="{Binding Text}"/>
<local:NumPad/>
</StackPanel>
NumPad.xaml is a UserControl that has 2 buttons.
What I want is when I click one of the buttons in LogInView I want to set the text of LogInViewModel to some string and when I click one of the buttons in MenuView I want to set the text of MenuViewModel to some string. I want to create a UserControl keyboard view with multiple buttons and be able to use it in multiple views but have it add characters(string) to the TextBox in the View they are located, I could create ICommands for every button in my ViewModels like so
class LogInViewModel : BaseObservableObject
{
public ICommand SetTextCommand1 { get; set; }
public ICommand SetTextCommand2 { get; set; }
private string _text;
public string Text
{
get { return _text; }
set { _text = value; OnPropertyChanged("Text"); }
}
public LogInViewModel()
{
SetTextCommand1 = new BaseICommand(SetText1);
SetTextCommand2 = new BaseICommand(SetText2);
}
private void SetText1(object obj)
{
Text = "1";
}
private void SetText2(object obj)
{
Text = "2";
}
}
and NumPad.xaml would be
<StackPanel>
<Button Command="{Binding SetTextCommand1}"/>
<Button Command="{Binding SetTextCommand2}"/>
</StackPanel>
and add the same ICommands to MenuViewModel, but it does not seem right because I want my NumPad.xaml to be a keyboard eventually with lots of buttons.
I think that if you would like to stick with commands, using single command + control it with parameter is better option, e.g.:
<StackPanel>
<Button Command="{Binding SetTextCommand}" CommandParameter="1" />
<Button Command="{Binding SetTextCommand}" CommandParameter="2" />
</StackPanel>
with
class LogInViewModel : BaseObservableObject
{
public ICommand SetTextCommand { get; set; }
private string _text;
public string Text
{
get { return _text; }
set { _text = value; OnPropertyChanged("Text"); }
}
public LogInViewModel()
{
SetTextCommand1 = new BaseICommand(SetText);
}
private void SetText(object obj)
{
Text = obj?.ToString();
}
}
Anyway, I would advice to refactor the solution to something cleaner, meaning e.g.:
your NumPad control shall define public event or command (like KeyPressed)
the control that embeds NumPad control (LogInView & MenuView in your case) shall explicitly use/bind the event or command of the NumPad
buttons in the NumPad xaml should use NumPad control's API only; specifying data binding in the reusable control in the way you did is similar to using "magic strings" (e.g. you have to make sure that each ViewModel must define command with the "SetTextCommand" name, it's simply error prone)
So that at the end, you use NumPad similar to:
<StackPanel>
<TextBox Text="{Binding Text}"/>
<local:NumPad KeyPressed="{Binding SetTextCommand}"/>
</StackPanel>

Enable Button on RelayCommand WPF

Button is not getting enable on Command Enable, doEnable method. Click method is disabling button.
Button IsEnabled is Bind with ViewModel public property IsEnable, which is setting true on doEnable.
Kindly advise what is wrong in below Code
XAML:
<Button Content="{Binding DataText}" Height="30" Width="80" Command="{Binding Enable}" Click="ButtonBase_OnClick" IsEnabled="{Binding IsEnable}" ></Button>
Window2.cs:
public partial class Window2 : Window
{
public Window2()
{
InitializeComponent();
DataContext = new ButtonEnableViewModel();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if(sender is Button btn)
btn.IsEnabled = false;
}
}
ButtonEnableViewModel.cs
class ButtonEnableViewModel : INotifyPropertyChanged
{
public ButtonEnableViewModel()
{
IsEnable = true;
DataText = "Click Here";
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChange(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private RelayCommand _enableCmd;
public RelayCommand Enable => _enableCmd ?? (_enableCmd = new RelayCommand(doEnable));
public bool IsEnable { get; set; }
public string DataText { get; set; }
protected void doEnable(object obj)
{
IsEnable = true;
DataText = "Clicked";
OnPropertyChange(nameof(IsEnable));
OnPropertyChange(nameof(DataText));
}
}
You should generally use the CanExecute method of the command to disable the Button. Most implementations of the ICommand interface accepts a Predicate<object> that you can use to tell the command when to enable the command/button.
In this case you might simply remove the event handler from the code-behind though and just handle the command in the view model:
<Button Content="{Binding DataText}" Height="30" Width="80" Command="{Binding Enable}" IsEnabled="{Binding IsEnable}" />
This should work since you set the IsEnable property in the Execute method of the command and raise the PropertyChanged event. You generally don't handle Click events in the code-behind of the view when you bind to a command of a view model.
If you use the CanExecute method of the command, you don't need to bind to the IsEnable property:
<Button Content="{Binding DataText}" Height="30" Width="80" Command="{Binding Enable}" />
View Model:
private RelayCommand _enableCmd;
public RelayCommand Enable => _enableCmd ?? (_enableCmd = new RelayCommand(doEnable, x => _isEnabled));
private bool _isEnabled;
public string DataText { get; set; }
protected void doEnable(object obj)
{
_isEnabled = true;
Enable.RaiseCanExecuteChanged();
DataText = "Clicked";
OnPropertyChange(nameof(DataText));
}

CanExecute() not enabling button when condition is met

I have a very simple application with a TextBox and a Button. When the text entered into the TextBox exceeds 5 characters in length, the button will be enabled. Here is the code for my ViewModel:
private string _text { get; set; }
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged("Text");
}
}
private ICommand _buttonCommand;
public ICommand ButtonCommand
{
get
{
if (_buttonCommand == null)
{
_buttonCommand = new RelayCommand(
param => this.ButtonCommandExecute(),
param => this.ButtonCommandCanExecute()
);
}
return _buttonCommand;
}
}
private bool ButtonCommandCanExecute()
{
if (this.Text.Length < 5)
{
return false;
}
else
{
return true;
}
}
private void ButtonCommandExecute()
{
this.Text = "Text changed";
}
public MainWindowViewModel()
{
//
}
The TextBox and Button are bound using this XAML:
<Button Content="Button" HorizontalAlignment="Left"
Margin="185,132,0,0" VerticalAlignment="Top" Width="120"
Command="{Binding Path=ButtonCommand}" />
<TextBox HorizontalAlignment="Left" Height="23"
Margin="185,109,0,0" TextWrapping="Wrap"
Text="{Binding Path=Text, Mode=TwoWay}" VerticalAlignment="Top" Width="120"/>
The DataContext appears to be set correctly, but here it is just because I am a WPF beginner:
private MainWindowViewModel view_model;
public MainWindow()
{
InitializeComponent();
view_model = new MainWindowViewModel();
this.DataContext = view_model;
}
When I type into the TextBox, the Button never enables.
Some implementations of ICommand interface have special method to notify whether "CanExecute" has changed. RelayCommand class (MVVM Light) has such method.
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged("Text");
// There is a special RelayCommand method to notify "CanExecute" changed.
// After this call, the "CanExecute" state is "re-evaluated" automatically by binding using CanExecute Func passed into RelayCommand constructor.
_buttonCommand.RaiseCanExecuteChanged();
}
}
private RelayCommand _buttonCommand;
public ICommand ButtonCommand
{
get
{
if (_buttonCommand == null)
{
_buttonCommand = new RelayCommand(
param => this.ButtonCommandExecute(),
param => this.ButtonCommandCanExecute()
);
}
return _buttonCommand;
}
}
This question can be useful: What is CanExecuteChanged for?
Actually you have to make Bool Property to bind to the IsEnabled property of the Button Control. And set this property to true when your text in Textbox is more than five character - you have to do this in Setter of Text property Because this is what being called when you type in your TextBox.
Basic About Commands :- These are basically to Report the e.g Clicks events to the C# code say your Viewmodel/Page.cs . So that you can perform some Tasks. It is not related to anything about Enabling and disabling of button.
Follow the Code :-
private string _text { get; set; }
public string Text
{
get { return _text; }
set
{
_text = value;
if(_text.Length > 5)
// Enable button here
// and command does not enable Buttons they are basically report the clicks events.
IsButtonEnabled = true;
OnPropertyChanged("Text");
}
}
For Enabling Button Create Bool type property Called IsButtonEnabled and bind this property to your Button in Xaml.
private bool _IsButtonEnabled { get; set; }
public bool IsButtonEnabled
{
get { return _IsButtonEnabled ; }
set
{
_IsButtonEnabled = value;
OnPropertyChanged("IsButtonEnabled");
}
}
In Xaml :-
<Button Content="Button" HorizontalAlignment="Left"
IsEnabled="{Binding IsButtonEnabled}"
Margin="185,132,0,0" VerticalAlignment="Top" Width="120"
Command="{Binding Path=ButtonCommand}" />
Try this Little modification of your code and tell me if it Works:
private string _text { get; set; }
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged("Text");
ButtonCommandCanExecute();
}
}
private ICommand _buttonCommand;
public ICommand ButtonCommand
{
get
{
if (_buttonCommand == null)
{
_buttonCommand = new RelayCommand(
param => this.ButtonCommandExecute(),
param => this.ButtonCommandCanExecute()
);
}
return _buttonCommand;
}
}
private bool ButtonCommandCanExecute()
{
if (this.Text.Length < 5)
{
return false;
}
else
{
return true;
}
}
private void ButtonCommandExecute()
{
this.Text = "Text changed";
}
public MainWindowViewModel()
{
//
}

Managing the IsEnabled property of a button

I have a xaml window in my program that has a button called "Save", and a textBox. I also have a ViewModel for this window. Inside the ViewModel I have a string property for the textBox, and a bool property for IsEnabled on the button. I would like the button to only be enabled when there is text inside the textBox.
xaml:
<Button IsEnabled="{Binding SaveEnabled}" ... />
<TextBox Text="{Binding Name}" ... />
ViewModel properties:
//Property for Name
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChange(() => Name);
if (value == null)
{
_saveEnabled = false;
NotifyPropertyChange(() => SaveEnabled);
}
else
{
_saveEnabled = true;
NotifyPropertyChange(() => SaveEnabled);
}
}
}
//Prop for Save Button -- IsEnabled
public bool SaveEnabled
{
get { return _saveEnabled; }
set
{
_saveEnabled = value;
NotifyPropertyChange(() => SaveEnabled);
}
}
I think my main question here is, where do I put the code concerning this problem? As you can see above, I've tried to put it into the setter of the Name property, but it comes back with no success.
You can just do:
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged(() => Name);
NotifyPropertyChanged(() => SaveEnabled);
}
}
public bool SaveEnabled
{
get { return !string.IsNullOrEmpty(_name); }
}
EDIT: Add this to your xaml:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}">...</TextBox>
Use ICommands that are used in MVVM:
private ICommand _commandSave;
public ICommand CommandSave
{
get { return _commandSave ?? (_commandSave = new SimpleCommand<object, object>(CanSave, ExecuteSave)); }
}
private bool CanSave(object param)
{
return !string.IsNullOrEmpty(Name);
}
private void ExecuteSave(object param)
{
}
And then use the following in the XAML Code
<TextBox Command="{Binding CommandSave}" ... />
Depending on the Framework that you use the command class works differen. For a generic implementation I suggest Relay Command.

Categories