Stopwatch MVVM Xamarin Forms - c#

It is my first time working in xamarin forms, I am now trying to make an app for myself. In my app I made a timer in my view model, but the seconds only go to 59 and reset automatically to 0. Does anyone have an idea how I can solve this and How I can make a stop button and a reset button my view model?
public class TimerPageViewModel : ViewModelBase, INotifyPropertyChanged
{
Stopwatch stopwatch = new Stopwatch();
private string _stopWatchHours;
private string _stopWatchMinutes;
private string _stopWatchSeconds;
public TimerPageViewModel(INavigationService navigationService)
: base(navigationService)
{
Title = "Timer";
Start = new Command(OnStartTimerExecute);
Stop = new Command(OnStop);
Reset = new Command(onReset);
StopWatchHours = stopwatch.Elapsed.Hours.ToString();
StopWatchMinutes = stopwatch.Elapsed.Minutes.ToString();
StopWatchSeconds = stopwatch.Elapsed.Seconds.ToString();
}
public string StopWatchHours
{
get { return _stopWatchHours; }
set { _stopWatchHours = value; OnPropertyChanged("StopWatchHours"); }
}
public string StopWatchMinutes
{
get { return _stopWatchMinutes; }
set { _stopWatchMinutes = value; OnPropertyChanged("StopWatchSeconds"); }
}
public string StopWatchSeconds
{
get { return _stopWatchSeconds; }
set { _stopWatchSeconds = value; OnPropertyChanged("StopWatchSeconds"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ICommand Start { get; set; }
public ICommand Stop { get; set; }
public ICommand Reset { get; set; }
private void OnStartTimerExecute()
{
stopwatch.Start();
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
StopWatchHours = stopwatch.Elapsed.Hours.ToString();
StopWatchMinutes = stopwatch.Elapsed.Minutes.ToString();
StopWatchSeconds = stopwatch.Elapsed.Seconds.ToString();
return true;
});
}
private void OnStop()
{
stopwatch.Stop();
stopwatch = null;
}
private void onReset()
{
stopwatch.Reset();
}
}
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="http://prismlibrary.com"
prism:ViewModelLocator.AutowireViewModel="True"
NavigationPage.HasNavigationBar="false"
x:Class="MyApp.Views.TimerPage"
IconImageSource="timer_icon_2x.png"
Title="{Binding Title}">
<StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
<Label HorizontalOptions="Center" FontSize="45" TextColor="#00A8E8">
<Label.FormattedText>
<FormattedString>
<Span Text="{Binding StopWatchHours}"/>
<Span Text=":"/>
<Span Text="{Binding StopWatchMinutes}"/>
<Span Text=":"/>
<Span Text="{Binding StopWatchSeconds}"/>
</FormattedString>
</Label.FormattedText>
</Label>
<Button Command="{Binding Start}"/>
<Button Command="{Binding Stop}"/>
<Button Command="{Binding Reset}"/>
</StackLayout>
</ContentPage>

you are raising OnPropertyChanged("StopWatchSeconds"); instead of OnPropertyChanged("StopWatchMinutes");
public string StopWatchMinutes
{
get { return _stopWatchMinutes; }
set { _stopWatchMinutes = value;
OnPropertyChanged("StopWatchSeconds");
}
}

Related

Xamarin Forms - How to pass data from a modal page back to parent page using Viewmodels

I have two pages: MainPage and FilterPage(modal page).
with their respective Viewmodels: MainViewModel and FilterViewModel.
In MainPage I have a listview that's populated with data from an API. The data is passed to the FilterPage where it is filtered by some specific criteria. In the end a new list is created which is assigned to the binded variable of the listview. What I noticed is that after the modal page closes the listview's items arent updated. What is the proper way to do this?
Model:
public class Multilist
{
public string Title { get; set; }
public string Date { get; set; }
public string Status { get; set; }
public string Customer { get; set; }
}
MainViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private IList<Multilist> mainList = new List<Multilist>();
public IList<Multilist> MainList
{
get => mainList;
set
{
if (value == mainList)
return;
mainList = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public MainViewModel(INavigation navigation)
{
this._navigation = navigation;
Task.Run(async () => await GetData());
GotoFilterPageCommand = new AsyncCommand(GotoFilterPage);
}
private async Task GetData()
{
//Gets data from API
MainList = data;
}
private async Task GotoFilterPage()
{
await this._navigation.PushModalAsync(new FilterPage(MainList.ToList()), true);
}
}
FilterViewModel:
public class FilterViewModel : INotifyPropertyChange
{
public List<Multilist> OldList { get; set; }
private IList<Multilist> mainList = new List<Multilist>();
public IList<Multilist> MainList
{
get => mainList;
set
{
if (value == mainList)
return;
mainList = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public FilterViewModel(List<Multilist> oldlist)
{
Oldlist = oldlist;
SomeCommand = new AsyncCommand(SomeTask);
}
private async Task SomeTask()
{
// Some code here
CreateNewList(OldList);
}
private async Task CreateNewList(List<Multilist> oldlist)
{
//Some code here --> newMainList
pageA.MainList = newMainList;
await App.Current.MainPage.Navigation.PopModalAsync();
}
}
The listview in MainPage:
<ListView x:Name="TestListView"
ItemsSource="{Binding MainList}"
Grid.Row="4" Grid.ColumnSpan="3"
HasUnevenRows="True"
>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="0,0,0,1">
<Grid VerticalOptions="Fill" Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="60"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="{Binding Title}" HorizontalTextAlignment="Start" VerticalTextAlignment="Center" Grid.Row="0" Grid.Column="0" />
<Label Text="{Binding Date}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Grid.Row="0" Grid.Column="1" />
<Label Text="{Binding Customer}" HorizontalTextAlignment="Start" VerticalTextAlignment="Center" Grid.Row="0" Grid.Column="2" />
<Label Text="{Binding Status}" HorizontalTextAlignment="End" VerticalTextAlignment="Center" Grid.Row="0" Grid.Column="3" />
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You could use Singleton to make a global instance for both MainViewModel and FilterViewModel.
I make a simple example for your reference.
Model:
public class Person
{
public string Name { get; set; }
public string FirstName { get; set; }
public int Age { get; set; }
}
ViewModel:
public class PersonViewModel
{
#region Singleton Pattern
private PersonViewModel()
{
Persons = new ObservableCollection<Person>()
{
new Person(){ Name="A"},
new Person(){ Name="A2"},
new Person(){ Name="A3"},
new Person(){ Name="A4"},
};
}
public static PersonViewModel Instance { get; } = new PersonViewModel();
#endregion
private ObservableCollection<Person> _person;
public ObservableCollection<Person> Persons
{
get { return _person; }
set { _person = value; }
}
}
Page24: //MainPage
<StackLayout>
<ListView ItemsSource="{Binding MainList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Text="Navigate To FilterPage" Clicked="Button_Clicked">
</Button>
</StackLayout>
Page24 Code behind:
public Page24()
{
InitializeComponent();
this.BindingContext = new Page24ViewModel();
}
private void Button_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new FilterPage());
}
Page24ViewModel://MainViewModel
public class Page24ViewModel : INotifyPropertyChanged
{
private PersonViewModel _personViewModel;
public Page24ViewModel()
{
_personViewModel = PersonViewModel.Instance;
}
private ObservableCollection<Person> mainList;
public ObservableCollection<Person> MainList
{
get { return _personViewModel.Persons; }
set
{
mainList = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
FilterPage:
<ContentPage.Content>
<StackLayout>
<StackLayout>
<Label Text="Name:"></Label>
<Entry x:Name="entry"></Entry>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Button x:Name="btn_Add" Text="Add" Clicked="btn_Add_Clicked"></Button>
<!--<Button x:Name="btn_Delete" Text="Delete" Clicked="btn_Delete_Clicked"></Button>-->
</StackLayout>
<ListView ItemsSource="{Binding MainList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding Name}"></Label>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
FilterPage code behind:
private PersonViewModel _personViewModel;
public FilterPage()//modal page
{
InitializeComponent();
this.BindingContext = new FilterViewModel();
}
private void btn_Add_Clicked(object sender, EventArgs e)
{
_personViewModel = PersonViewModel.Instance;
_personViewModel.Persons.Add(new Person() { Name = entry.Text });
}
FilterViewModel:
public class FilterViewModel : INotifyPropertyChanged
{
private PersonViewModel _personViewModel;
public FilterViewModel()
{
_personViewModel = PersonViewModel.Instance;
}
private ObservableCollection<Person> newMainList;
public ObservableCollection<Person> MainList
{
get { return _personViewModel.Persons; }
set
{
newMainList = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}

Xamarin.Forms Update Binding Label inside CarouselView

I have this label like so:
<Label x:Name="QuestionText" FontSize="62" Text="{Binding question}" HorizontalTextAlignment="Center" Padding="0,20,0,0" />
This is inside a CarouselView:
<CarouselView
x:Name="Questions"
HeightRequest="475">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="Center" Padding="10">
<Label x:Name="QuestionText" FontSize="62" Text="{Binding question}" HorizontalTextAlignment="Center" Padding="0,20,0,0" />
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
And I am populating the CarouselView like so:
protected override async void OnAppearing()
{
base.OnAppearing();
questions = await webService.GetTaskQuestions(taskcategory);
List<QuestionsClass> currentPage = new List<QuestionsClass>();
currentPage.Add(questions[currentPageIndex]);
Questions.ItemsSource = currentPage;
}
And I am trying to update my text like so:
questions[currentPageIndex].question = questions[currentPageIndex].question.Replace("_", questions[currentPageIndex].answer);
I have even tried:
Device.BeginInvokeOnMainThread(() =>
{
questions[currentPageIndex].question = questions[currentPageIndex].question.Replace("_", questions[currentPageIndex].answer);
});
And still nothing, here is my class:
public class QuestionsClass
{
public string question { get; set; }
public string answer { get; set; }
}
How do I update a label inside a CarouselView?
UPDATE
I have tried with INotifyPropertyChanged:
public class QuestionsClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string question { get; set; }
public string actualQuestion
{
get
{
return question;
}
set
{
question = value;
OnPropertyChanged();
}
}
public string answer { get; set; }
protected void OnPropertyChanged([CallerMemberName] string quetion = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(question));
}
}
Here is my label:
<Label FontSize="62" Text="{Binding actualQuestion}" HorizontalTextAlignment="Center" Padding="0,20,0,0" />
and here is how I am updating it:
questions[currentPageIndex].question = questions[currentPageIndex].question.Replace("_", questions[currentPageIndex].answer);
UPDATE
My class was wrong, this is the correct class and is now working:
public class QuestionsClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _question;
public string question {
get
{
return _question;
}
set
{
_question = value;
NotifyPropertyChanged("question");
}
}
public string answer { get; set; }
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

How to use Command on a Label with GestureRecognizers?

I am trying to make when clicking on a Label enter text in an Entry. I am doing it with TapGestureRecognizer and command but it does nothing, I don't know where the problem can be. The x:Name of the Entry is EntryControl.
My Label:
<Label Margin="10"
FontSize="30"
Text="{Binding EmojiSource}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EmojiTappedCommand}" />
</Label.GestureRecognizers>
</Label>
Behind Code:
public ICommand EmojiTappedCommand { get; private set; }
public Editor(){
EmojiTappedCommand = new Command(EmojiButtonCommand);
}
private void EmojiButtonCommand()
{
EntryControl.Text ="Tapped";
}
Please add BindingContext = this; in the background code.
public partial class MainPage : ContentPage
{
public ICommand EmojiTappedCommand { get; private set; }
public string EmojiSource { get; set; }
public MainPage()
{
InitializeComponent();
EmojiSource = "test";
EmojiTappedCommand = new Command(EmojiButtonCommand);
BindingContext = this;
}
private void EmojiButtonCommand()
{
EntryControl.Text = "Tapped";
}
Here is running GIF.

Xamarin Forms Binding with namespace declaration error

I'm new to Xamarin and I'm trying to bind my ViewModel to the View but I couldn't do it yet.
Here's the code.
(Model)
namespace CadastroProdutos
{
public class Produto
{
public string Codigo { get; set; }
public string Identificacao { get; set; }
public string Tipo { get; set; }
}
}
(Observable Model)
namespace CadastroProdutos
{
public class ObservableProduto : INotifyPropertyChanged
{
Produto produto;
public ObservableProduto()
{
}
public event PropertyChangedEventHandler PropertyChanged;
public string Codigo
{
set
{
if (!value.Equals(produto.Codigo, StringComparison.Ordinal))
{
produto.Codigo = value;
OnPropertyChanged("Codigo");
}
}
get
{
return produto.Codigo;
}
}
public string Identificacao
{
set
{
if (!value.Equals(produto.Identificacao, StringComparison.Ordinal))
{
produto.Identificacao = value;
OnPropertyChanged("Identificacao");
}
}
get
{
return produto.Identificacao;
}
}
public string Tipo
{
set
{
if (!value.Equals(produto.Tipo, StringComparison.Ordinal))
{
produto.Tipo = value;
OnPropertyChanged("Tipo");
}
}
get
{
return produto.Tipo;
}
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler == null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
(ViewModel)
namespace CadastroProdutos
{
public class ListProdutoViewModel
{
ObservableCollection<ObservableProduto> produtos;
public ListProdutoViewModel()
{
produtos = new ObservableCollection<ObservableProduto>();
}
public ObservableCollection<ObservableProduto> Produtos
{
set
{
if (value != produtos)
{
produtos = value;
}
}
get
{
return produtos;
}
}
}
}
(View)
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:CadastroProdutos;assembly=CadastroProdutos"
x:Class="CadastroProdutos.ListProduto"
Title="Listagem de Produtos">
<ContentPage.Content>
<ListView x:Name="listView" Margin="20,40,20,20" ItemsSource="{Binding Produtos}">
<ListView.BindingContext>
<local:ListProdutoViewModel />
</ListView.BindingContext>
<ListView.Header>
<StackLayout Orientation="Vertical" >
<Label Text="Produtos" HorizontalOptions="Center"/>
</StackLayout>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal" >
<TextCell Text="{Binding Identificacao}"/>
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
Getting an error "Xamarin.Forms.Xaml.XamlParseException Position 10:6. Type local:ListProdutoViewModel not found in xmlns clr-namespace:CadastroProdutos;assembly=CadastroProdutos".
What am I missing on the namespace declaration?
Thanks in advance.
Ensure that whether ListProdutoViewModel is deifined under the namespace - CadastroProdutos.
Also, you no need to mention the assembly there in, local: assembly=CadastroProdutos. So try to run the application after removing the above assembly code. Like as below,
local="clr-namespace:CadastroProdutos"

Xamarin Set Binding of EntryCell

I want to save the values that a user enters in the placeholder of an EntryCell using MVVM.
This is part of my .xaml
<TableView>
<TableView.Root>
<TableSection>
<EntryCell x:Name="HomeEC"
Label="HomeTeam"
Placeholder="{Binding Home, Mode=TwoWay}"
>
</EntryCell>
<EntryCell x:Name="AwayEC"
Label="AwayTeam"
Placeholder="{Binding Away, Mode=TwoWay}"
>
</EntryCell>
<EntryCell x:Name="BetEC"
Label="BetTeam"
Placeholder="{Binding Bet, Mode=TwoWay}"
>
</EntryCell>
<EntryCell x:Name="TypeEC"
Label="BetType"
Placeholder="{Binding Type, Mode=TwoWay}"
>
</EntryCell>
<EntryCell x:Name="OddEC"
Label="Odd"
Placeholder="{Binding Odd, Mode=TwoWay}"
>
</EntryCell>
</TableSection>
</TableView.Root>
</TableView>
And this is my ViewModel class
public string Home
{
set
{
home = value;
OnPropertyChanged("Home");
newMatch.HomeTeam = home;
}
}
public string Away
{
set
{
away = value;
OnPropertyChanged("Away");
newMatch.AwayTeam = away;
}
}
public string Bet
{
set
{
bet = value;
OnPropertyChanged("Bet");
newMatch.Bet = bet;
}
}
public string Type
{
set
{
type = value;
OnPropertyChanged("Type");
newMatch.BetType = type;
}
}
public string Odd
{
set
{
odd = value;
OnPropertyChanged("Odd");
newMatch.Odd = Decimal.Parse(odd);
}
}
public ICommand InsertBet;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
When I enter my values in the field in the UI, they do not get saved here in the VM. What am I doing wrong?
Thanks,
Dragos
InsertMatchVM.cs
public class InsertMatchVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string home, away, bet;
public Match newMatch = new Match();
public string Home
{
set
{
home = value;
OnPropertyChanged("Home");
newMatch.HomeTeam = home;
}
get
{
return home;
}
}
public string Away
{
set
{
away = value;
OnPropertyChanged("Away");
newMatch.AwayTeam = away;
}
get
{
return away;
}
}
public string Bet
{
set
{
bet = value;
OnPropertyChanged("Bet");
newMatch.Bet = bet;
}
get
{
return bet;
}
}
public ICommand InsertBet;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
Page1.Xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App2;assembly=App2"
x:Class="App2.Page1">
<ContentPage.BindingContext>
<local:InsertMatchVM/>
</ContentPage.BindingContext>
<TableView>
<TableView.Root>
<TableSection>
<EntryCell x:Name="HomeEC"
Label="HomeTeam"
Text="{Binding Home, Mode=TwoWay}"
Placeholder="Home"
>
</EntryCell>
<EntryCell x:Name="AwayEC"
Label="AwayTeam"
Text="{Binding Away, Mode=TwoWay}"
Placeholder="Away"
>
</EntryCell>
<EntryCell x:Name="BetEC"
Label="BetTeam"
Text="{Binding Bet, Mode=TwoWay}"
Placeholder="Bet"
>
</EntryCell>
</TableSection>
</TableView.Root>
</TableView>
</ContentPage>
App.cs
public class App : Application
{
public App()
{
MainPage = new Page1();
}
}
Match.cs
public class Match
{
public string HomeTeam { get; set; }
public string AwayTeam { get; set; }
public string Bet { get; set; }
}

Categories