I have a form where a user can enter some data and save it to a database. I have been trying to add basic validation, making a user have to enter a value in every field. I want my save button to be unavailable until the user has entered some information in every form.
Here is my button:
<ToolbarItem Name="MenuItem1" Order="Primary" Text="Save" Priority="1" Command="{Binding SaveDataCommand}" IsEnabled="{Binding CanSaveData}"/>
I can access the isEnabled property in the XAML but where this gets infuriating is that I can't then reupdate this property. The button gets stuck in whatever state I tell it on load. I have checked my view model and it is returning a boolean which is correct (printing it to the console), it's just there is no way of updating the button state.
I even tried adding a new button that would force update the enabled state however this wouldn't work
<ToolbarItem x:Name="whyxamarinwhy" Name="MenuItem1" Order="Primary" Text="Save" Priority="1" Command="{Binding SaveEvent}" IsEnabled="{Binding CanCreateEvent}"/>
whyxamarinwhy.isenabled = true; //I cannot access this property because it doesn't exist.
Is the only way to implement this functionality going to be using a custom renderer?
MenuItem.IsEnabledProperty / For internal use by the Xamarin.Forms platform.
You should use the Command's CanExecute of the ToolbarItem to determine if the Execute method can be triggered.
Since you are already binding the command to SaveDataCommand, you can toggle the return of the CanExecute of that command to determine if the menu item button should be allowed to trigger the attached command.
You did not post your ViewModel, but in a inner-class ICommand implementation, something as simple as this works:
static bool SaveDataCommandCanExecute = true;
class SaveDataCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
// your code needs to toggle SaveDataCommandCanExecute to determine if the Execute method can be triggered
return SaveDataCommandCanExecute;
}
public void Execute(object parameter)
{
// Do something
}
}
Related
I am working with Xamarin Prism, I'm trying to disable the button, but whenever I add the command it enables. What can this be ?
Code:
<Button
Command="{Binding save}"
IsEnabled="False"/>
From the document of Button.Command Property we can see:
This property is used to associate a command with an instance of a
button. This property is most often set in the MVVM pattern to bind
callbacks back into the ViewModel. IsEnabled is controlled by the
Command if set.
So, if you don't set the command property, it will work correctly.
If you set the command property, the isEnable is controlled by the Command itself.
Here I wrote an example to show you how to control the button's enable property in the command:
In the xaml:
<ContentPage.BindingContext>
<local:CommandDemoViewModel />
</ContentPage.BindingContext>
<StackLayout>
<Button Text="Divide by 2"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Command="{Binding DivideBy2Command}"
/>
</StackLayout>
And the ViewModel:
class CommandDemoViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Command DivideBy2Command { private set; get; }
public CommandDemoViewModel()
{
DivideBy2Command = new Command(()=> performCommandAction(), ()=>isButtonEnable());
}
private bool isButtonEnable()
{
//Handle the if the button isEnable here
//Reture ture or false with your own logic here to control the button's isEnabled
return false;
}
private void performCommandAction()
{
//Handle the button click logic here
}
}
Use the constructor public Command(Action execute, Func<bool> canExecute);.
You can control the isEnable by the command's CanExecute. Reture ture or false with your own logic in the method isButtonEnable to control the button's isEnabled.
My application viewModel responds to a user clicking a button to see test results:
private void AddDetailRows(List<QuizHistory> quizHistoryList)
{
quizDetails.Children.Clear();
quizDetails.Children.Add(AddData(quizHistoryList));
quizDetails.Children.Add(new LineTemplate());
}
Where quizDetails is the name of an element in the view.
But this doesn't work for me as the view model doesn't know what the view looks like and does not have access to the names of elements.
In a MVVM application, how is this problem solved?
You are completely right, that is not something that ViewModel is responsible of.
So, whatever you want to do with UI is not responsibility of the ViewModel.
If this is really the only option, then you can think of creating boolean properties in your VM and binding them to your views and then changing that boolean from false to true or vice versa on button click command which is binded to your VM.
To simplify it:
MyView.xaml
<StackLayout>
<Button Command="{Binding ShowHideQuizHistoryCommand}" ... />
<StackLayout x:Name="QuizHistory"
IsVisible={Binding ShowQuizHistory }>
//
</StackLayout>
</StackLayout>
MyViewModel.cs
private bool _showQuizHistory ;
public bool ShowQuizHistory
{
get { return _showQuizHistory ; }
set
{
_showQuizHistory = value;
OnPropertyChanged();
}
}
public ICommand ShowHideQuizHistoryCommand => new Command(() =>
{
ShowQuizHistory = !ShowQuizHistory;
});
So, this is just an example based on what you provided in question.
You can also use visual states, converters, triggers and behaviors in order to achieve this, but in my opinion this is the easiest way.
currently I don't know how to solve my problem. I am writing an app with xamarin.forms.
I have a view which contains the userprofile of the user. For example there is a entry with the username. The field is binded in the twoway mode to the viewmodel:
<Entry x:Name="givennameSurname" Text="{Binding FullName, Mode=TwoWay}" />
The whole userprofile contains some fields which are validated with a behavior:
<behaviors:TelNumBehavior x:Name="NumValidatorUser" IsValid="{Binding Source={x:Reference root}, Path=BindingContext.UserTelNumBehavior, Mode=TwoWay }"></behaviors:TelNumBehavior>
The isValid property is also binded to the viewmodel.
What I want to achieve? I want, that if one of the behaviors validate the input to false, that the userprofile can not be updated.
So I wanted to create a command on a button. The button has an canExecute method: this method checks the booleans in the viewmodel if they all are true or false. If true, I want to force a refresh of the data of viewmodel from the view. I use that canExecute also to prevent unwanted changes, if the UI is in the state, that some input is wrong:
public string FullName
{
get => profile.GivenName;
set
{
if (CanSave())
{
profile.GivenName = value;
OnPropertyChanged();
}
}
}
What is the problem?
I change an input with a behavior, so that the behavior says the input is wrong. Then I edit the username. Then I change the input of the wrong-behavior to true. Now the input of the username does not refresh in the viewmodel. I want to refresh it with the command, but I don't know how to force a refresh from the view to the viewmodel.
As far as I know you need to write what property you changing in your getter and setter so:
OnPropertyChanged("FullName");
I created a form with listview and ISingleOperation fo data refresh.
Then i created command in ViewModel.
public IRelayCommand LoadInvoicesCommand
{
get
{
return GetCommand(() => Execution.ViewModelExecute(new LoadInvoicesOperation(_model), 10000));
}
}
ISingleOperation works well and returns
new Result() { ResultAction = ResultType.None };
Refresh operation is bound well
RefreshCommand="{Binding LoadInvoicesCommand}"
But refresh indicator "hangs" and not disapearing, what is wrong here?
You need to bind a second property from the ListView named IsRefreshing to your ViewModel. This is a boolean property and is the one responsible to tell the ListView that the refreshing has started/completed.
An example of a ListView XAML
<ListView
VerticalOptions="FillAndExpand"
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding LoadInvoicesCommand}"
IsRefreshing="{Binding IsRefreshing, Mode=OneWay}"
ItemsSource="{Binding YourItemSource}"
ItemTemplate="{StaticResource ItemTemplate" />
Your ViewModel will need a public property called IsRefreshing and you will need to set this to false when you the refresh command has completed.
Scenario: I start out in my app's main page. I navigate to sub-page A, change a value, hit the back button and the bound TextBlock in the main page doesn't change. If I navigate to sub-page B, a TextBlock using that same binding changes. Likewise, if I go to page A again I see the changed value. If I exit the app, the new value shows up on the main page. It's just when using the back button that a refresh doesn't get triggered.
I've got all my INotifyPropertyChanged stuff working. Like I said, the binding works in every scenario besides navigating back to the main page. How do I send a message or otherwise trigger a refresh of the bindings on that page? Thanks!
Edit:
Based on the accepted answer from willmel, here's what I did:
My MainPage.xaml file has this markup:
<TextBlock Text="{Binding Title, Mode=OneWay}" />
My MainViewModel.cs file has this:
public string Title
{
get { return ProfileModel.Instance.DescriptionProfile.Title; }
}
And I added this to the MainViewModel constructor:
Messenger.Default.Register<PropertyChangedMessage<string>>(this,
(action) => DispatcherHelper.CheckBeginInvokeOnUI(
() => RaisePropertyChanged("Title")));
In another view I have the following markup:
<TextBox Grid.Row="1" Width="250" Height="100" Text="{Binding TitleEdit, Mode=TwoWay}" />
In its view model I use this when getting/setting a string:
public string TitleEdit
{
get { return ProfileModel.Instance.DescriptionProfile.Title; }
set
{
if (ProfileModel.Instance.DescriptionProfile.Title == value) return;
string oldValue = ProfileModel.Instance.DescriptionProfile.Title;
ProfileModel.Instance.DescriptionProfile.Title = value;
RaisePropertyChanged("Title", oldValue, value, true);
}
}
In your view model you want to be modified if a child page changes a property. (note here, the property is of type bool, but could be anything)
Messenger.Default.Register<PropertyChangedMessage<bool>>(this,
(action) => DispatcherHelper.CheckBeginInvokeOnUI(
() =>
{
MessageBox.Show(action.newValue.ToString());
//do what you want here (i.e. RaisePropertyChanged on a value they share)
}));
When you use RaisePropertyChanged in the child class, use the broadcasting overload.
RaisePropertyChanged("Preference", oldValue, value, true);
Finally, note that to use DispatcherHelper, you need to Add the following to your App constructor (App.xaml.cs)
DispatcherHelper.Initialize();