Reputation: 21
I want to make a simple Stopwatch app with xamarin forms.
Here is the interface :
<?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:testvb"
x:Class="testvb.MainPage">
<StackLayout>
<Label Text="{Binding LabelText}">
<Label.BindingContext>
<local:GetDuration/>
</Label.BindingContext>
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand}">
</TapGestureRecognizer>
</Label.GestureRecognizers>
</Label>
<Label Text="{Binding SWDuration}">
<Label.BindingContext>
<local:GetDuration/>
</Label.BindingContext>
</Label>
<Label Text="{Binding StartTimeText}">
<Label.BindingContext>
<local:GetDuration/>
</Label.BindingContext>
</Label>
</StackLayout>
</ContentPage>
And the c#-class:
namespace testvb
{
class GetDuration : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
static string labeltext = "START";
static string starttimetext = "Timer hasn't been started";
ICommand tapCommand;
static Stopwatch hstopwatch = new Stopwatch();
string swduration;
public GetDuration()
{
tapCommand = new Command(OnTapped);
this.SWDuration = hstopwatch.Elapsed.ToString();
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
this.SWDuration = hstopwatch.Elapsed.ToString();
return true;
});
}
public ICommand TapCommand
{
get
{
return tapCommand;
}
}
void OnTapped (object s)
{
if(LabelText == "START")
{
LabelText = "STOP";
StartTimeText = DateTime.Now.ToString();
hstopwatch.Restart();
}
else
{
LabelText = "START";
StartTimeText = "Timer has been stopped";
hstopwatch.Stop();
hstopwatch.Reset();
}
}
public string LabelText
{
get
{
return labeltext;
}
set
{
if(labeltext != value)
{
labeltext = value;
if(PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs("LabelText"));
}
}
}
}
public string SWDuration
{
get
{
return swduration;
}
set
{
if(swduration!=value)
{
swduration = value;
if(PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SWDuration"));
}
}
}
}
public string StartTimeText
{
get
{
return starttimetext;
}
set
{
if (starttimetext != value)
{
starttimetext = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("StartTimeText"));
}
}
}
}
}
}
As from interface I have a label (toogle between start and stop when it is clicked), a label to show the duration, and a label to show the time when the stopwatch was started.
The output on xamarin android simulator:_
I don't understand why it can't work for the last label since the principal ist the same as the first label (start/stop). I would thank you if someone can point out where I misunderstand it or on which part I did wrong on my code.
Thank you.
Upvotes: 0
Views: 1548
Reputation: 9742
By setting the BindingContext
with
<Label.BindingContext>
<local:GetDuration/>
</Label.BindingContext>
separately on each Label
you are creating a new instance of GetDuration
for each label. Hence the instance you are invoking TapCommand
on will be different to the one that is attached to your labels.
By changeing your XAML to
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:stopwatch"
x:Class="stopwatch.MainPage">
<ContentPage.BindingContext>
<local:GetDuration></local:GetDuration>
</ContentPage.BindingContext>
<StackLayout>
<Label Text="{Binding LabelText}">
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand}">
</TapGestureRecognizer>
</Label.GestureRecognizers>
</Label>
<Label Text="{Binding SWDuration}" />
<Label Text="{Binding StartTimeText}" />
</StackLayout>
</ContentPage>
you are creating a single GetDuration
as the binding context of your page. Unless explicitly set otherwise, the BindingContext
will be passed to the children of your page, hence there is no need, to set it for each label (disregarding the fact that it did not work anyway).
Further remarks
Event Invocator
You are raising the event manually from each of your properties. Creating an event invocator that does this for you will save you some lines and make you adhere to DRY more
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Because of the CallerMemberName
attribute, the name of the calling property will be passed automagically (it's syntactic sugar after all and the compiler will do the magic for you). The properties will become
public string StartTimeText
{
get
{
return starttimetext;
}
set
{
if (starttimetext != value)
{
starttimetext = value;
this.OnPropertyChanged();
}
}
}
LabelText for the decision
void OnTapped(object s)
{
if (LabelText == "START")
{
LabelText = "STOP";
StartTimeText = DateTime.Now.ToString();
hstopwatch.Restart();
}
else
{
LabelText = "START";
StartTimeText = "Timer has been stopped";
hstopwatch.Stop();
hstopwatch.Reset();
}
}
It may be a matter of taste, but I would not let the decision whether to start or to stop the time depend on the value of LabelText
, but rather on an internal bool
or enum
. The way you are doing it, you internal and external behavior. It's OOP after all and you should make use of encapsulation.
Upvotes: 2