There is a question with an answer that shows how a progress bar can be created that runs for a specified period of time. Here's a link to that question:
How can I create a bar area that slowly fills from left to right over 5, 10 or ?? seconds?
I have tested this out and it works well. However I would like to find out how I can extend this so that the progress bar can be cancelled / stopped before completed and then restarted again.
The question and answer were very popular so it seems like this is something that might benefit many people.
I would appreciate any ideas and feedback on possible ways this could be done.
Update 1:
I tried to implement the solution but I am getting an error and would appreciate some advice. I'm using all your new code and I change from the old to the new here:
<local:TimerView x:Name="timerView"> <local:TimerView.ProgressBar> <BoxView BackgroundColor="Maroon" /> </local:TimerView.ProgressBar> <local:TimerView.TrackBar> <BoxView BackgroundColor="Gray" /> </local:TimerView.TrackBar> </local:TimerView> <!--<Grid x:Name="a"> <local:TimerView x:Name="timerView1" VerticalOptions="FillAndExpand"> <local:TimerView.ProgressBar> <Frame HasShadow="false" Padding="0" Margin="0" BackgroundColor="#AAAAAA" CornerRadius="0" VerticalOptions="FillAndExpand" /> </local:TimerView.ProgressBar> <local:TimerView.TrackBar> <Frame HasShadow="false" Padding="0" Margin="0" CornerRadius="0" BackgroundColor="#EEEEEE" VerticalOptions="FillAndExpand" /> </local:TimerView.TrackBar> </local:TimerView> </Grid> <Grid x:Name="b"> <local:TimerView x:Name="timerView2" VerticalOptions="FillAndExpand"> <local:TimerView.ProgressBar> <Frame HasShadow="false" Padding="0" Margin="0" BackgroundColor="#AAAAAA" CornerRadius="0" VerticalOptions="FillAndExpand" /> </local:TimerView.ProgressBar> <local:TimerView.TrackBar> <Frame HasShadow="false" Padding="0" Margin="0" CornerRadius="0" BackgroundColor="#EEEEEE" VerticalOptions="FillAndExpand" /> </local:TimerView.TrackBar> </local:TimerView> </Grid>-->
Three questions
First - I noticed you split timerView into two files. The properties file appears to be in some way linked to the main file. Graphically the properties file appears indented from timerView. How do you do this linking in Visual Studio? I just created two files, does that make a difference.
Second - When I try to compile the code I am getting this error:
/Users//Documents/Phone app/Japanese7/Japanese/Views/Phrases/PhrasesFrame.xaml(10,10): Error: Position 117:10. Missing a public static GetProgressBar or a public instance property getter for the attached property "Japanese.TimerView.ProgressBarProperty" (Japanese)
Do you have any ideas what might be causing this? Everything looks the same as before.
Third - I notice you use BoxView and I used a Frame. Would the code work with either?
Update 2:
In my backend C# code I use the following to start the timer:
timerView.StartTimerCommand .Execute(TimeSpan.FromSeconds(App.pti.Val()));
I tried to stop the timer with some similar syntax but there's some problem. Can you let me know how I can go about stopping the timer when it's used with C# back-end rather than the MVVM in your solution:
timerView.StopTimerCommand.Execute(); // Give syntax error
1 Answers
Answers 1
Step 1: Add cancel method to ViewExtensions
:
public static class ViewExtensions { static string WIDTH_ANIMATION_NAME = "WidthTo"; public static Task<bool> WidthTo(this VisualElement self, double toWidth, uint length = 250, Easing easing = null) { ... } public static void CancelWidthToAnimation(this VisualElement self) { if(self.AnimationIsRunning(WIDTH_ANIMATION_NAME)) self.AbortAnimation(WIDTH_ANIMATION_NAME); } }
Step 2: Add bindable properties for 'pause' and 'stop'/'cancel' commands; and a property to track whether timer is running.
public static readonly BindableProperty PauseTimerCommandProperty = BindableProperty.Create( "PauseTimerCommand", typeof(ICommand), typeof(TimerView), defaultBindingMode: BindingMode.OneWayToSource, defaultValue: default(ICommand)); public ICommand PauseTimerCommand { get { return (ICommand)GetValue(PauseTimerCommandProperty); } set { SetValue(PauseTimerCommandProperty, value); } } public static readonly BindableProperty StopTimerCommandProperty = BindableProperty.Create( "StopTimerCommand", typeof(ICommand), typeof(TimerView), defaultBindingMode: BindingMode.OneWayToSource, defaultValue: default(ICommand)); public ICommand StopTimerCommand { get { return (ICommand)GetValue(StopTimerCommandProperty); } set { SetValue(StopTimerCommandProperty, value); } } public static readonly BindableProperty IsTimerRunningProperty = BindableProperty.Create( "IsTimerRunning", typeof(bool), typeof(TimerView), defaultBindingMode: BindingMode.OneWayToSource, defaultValue: default(bool), propertyChanged: OnIsTimerRunningChanged); public bool IsTimerRunning { get { return (bool)GetValue(IsTimerRunningProperty); } set { SetValue(IsTimerRunningProperty, value); } } private static void OnIsTimerRunningChanged(BindableObject bindable, object oldValue, object newValue) { ((TimerView)bindable).OnIsTimerRunningChangedImpl((bool)oldValue, (bool)newValue); }
Step 3: Update TimerView
as below to use a StopWatch
to track time, pause, and cancel.
public partial class TimerView : AbsoluteLayout { readonly Stopwatch _stopWatch = new Stopwatch(); public TimerView() { ... } async void HandleStartTimerCommand(object param = null) { if (IsTimerRunning) return; ParseForTime(param); if (InitRemainingTime()) _stopWatch.Reset(); SetProgressBarWidth(); IsTimerRunning = true; //Start animation await ProgressBar.WidthTo(0, Convert.ToUInt32(RemainingTime.TotalMilliseconds)); //reset state IsTimerRunning = false; } void HandlePauseTimerCommand(object unused) { if (!IsTimerRunning) return; ProgressBar.CancelWidthToAnimation(); //abort animation } void HandleStopTimerCommand(object unused) { if (!IsTimerRunning) return; ProgressBar.CancelWidthToAnimation(); //abort animation ResetTimer(); //and reset timer } protected virtual void OnIsTimerRunningChangedImpl(bool oldValue, bool newValue) { if (IsTimerRunning) { _stopWatch.Start(); StartIntervalTimer(); //to update RemainingTime } else _stopWatch.Stop(); ((Command)StartTimerCommand).ChangeCanExecute(); ((Command)PauseTimerCommand).ChangeCanExecute(); ((Command)StopTimerCommand).ChangeCanExecute(); } bool _intervalTimer; void StartIntervalTimer() { if (_intervalTimer) return; Device.StartTimer(TimeSpan.FromMilliseconds(100), () => { if(IsTimerRunning) { var remainingTime = Time.TotalMilliseconds - _stopWatch.Elapsed.TotalMilliseconds; if (remainingTime <= 100) { _intervalTimer = false; ResetTimer(); } else RemainingTime = TimeSpan.FromMilliseconds(remainingTime); } return _intervalTimer = IsTimerRunning; //stop device-timer if timer was stopped }); } private void ResetTimer() { ProgressBar.CancelWidthToAnimation(); RemainingTime = default(TimeSpan); //reset timer SetProgressBarWidth(); //reset width } void SetProgressBarWidth() { if (RemainingTime == Time) SetLayoutBounds(ProgressBar, new Rectangle(0, 0, Width, Height)); else { var progress = ((double)RemainingTime.Seconds / Time.Seconds); SetLayoutBounds(ProgressBar, new Rectangle(0, 0, Width * progress, Height)); } } ... }
Sample Usage
<controls:TimerView x:Name="timerView"> <controls:TimerView.ProgressBar> <BoxView BackgroundColor="Maroon" /> </controls:TimerView.ProgressBar> <controls:TimerView.TrackBar> <BoxView BackgroundColor="Gray" /> </controls:TimerView.TrackBar> </controls:TimerView> <Label Text="{Binding Path=RemainingTime, StringFormat='{0:%s}:{0:%f}', Source={x:Reference timerView}}" /> <Button Command="{Binding StartTimerCommand, Source={x:Reference timerView}}" Text="Start Timer"> <Button.CommandParameter> <x:TimeSpan>0:0:20</x:TimeSpan> </Button.CommandParameter> </Button> <Button Command="{Binding PauseTimerCommand, Source={x:Reference timerView}}" Text="Pause Timer" /> <Button Command="{Binding StopTimerCommand, Source={x:Reference timerView}}" Text="Stop Timer" />
Working sample uploaded at TimerBarSample
EDIT 1
First - It really doesn't make a difference - you can even merge all code into one file. Indented linking can be achieved using <DependentOn />
tag - similar to what is used for code-behind cs
for XAML files.
Second - I had added protected
access-modifiers to bindable properties' getters or setters. But looks like it fails when XAMLC is applied. I have updated the code in the github sample.
Third - Yes, any control that inherits from View
(be it be BoxView
or Frame
) can be used.
EDIT 2
As these commands (bindable properties) are of type ICommand
, in order to Execute
- you need to pass in a parameter. In case the command doesn't need a parameter - you can use null
.
Recommended usage:
if(timerView.StopTimerCommand.CanExecute(null)) timerView.StopTimerCommand.Execute(null);
0 comments:
Post a Comment