答案中有一个问题,显示如何创建运行指定时间段的进度条。以下是该问题的链接:
How can I create a bar area that slowly fills from left to right over 5, 10 or ?? seconds?
我已经对此进行了测试,效果很好。但是,我想知道如何扩展它,以便可以在完成之前取消/停止进度条,然后再次重新启动。
问题和答案非常受欢迎,所以看起来这可能会让很多人受益。
我很感激有关可能的方法的任何想法和反馈。
更新1:
我尝试实施解决方案,但我收到错误,并希望得到一些建议。我正在使用你所有的新代码,我在这里改变了新的代码:
timerView.StartTimerCommand
.Execute(TimeSpan.FromSeconds(App.pti.Val()));
三个问题
首先 - 我注意到你将timerView分成两个文件。属性文件似乎以某种方式链接到主文件。从图形上看,属性文件从timerView缩进。你如何在Visual Studio中进行此链接?我刚创建了两个文件,这有什么不同。
第二次 - 当我尝试编译代码时,我收到此错误:
/ Users // Documents / Phone app / Japanese7 / Japanese / Views / Phrases / PhrasesFrame.xaml(10,10):错误:位置117:10。缺少附加属性“Japanese.TimerView.ProgressBarProperty”(日语)的公共静态GetProgressBar或公共实例属性getter
你有什么想法可能导致这个吗?一切都和以前一样。
第三次 - 我注意到你使用的是BoxView而我使用的是Frame。代码是否可以使用?
更新2:
在我的后端C#代码中,我使用以下命令启动计时器:
timerView.StopTimerCommand.Execute(); // Give syntax error
我试图用一些类似的语法来停止计时器,但是有一些问题。您是否可以告诉我如何在与C#后端使用计时器而不是解决方案中的MVVM时停止计时器:
{{1}}
答案 0 :(得分:3)
第1步:将取消方法添加到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);
}
}
第2步:为'pause'和'stop'/'cancel'命令添加可绑定属性;以及跟踪计时器是否正在运行的属性。
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);
}
第3步:更新TimerView
,如下所示,使用StopWatch
来跟踪时间,暂停和取消。
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));
}
}
...
}
<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" />
首先 - 它确实没有什么区别 - 您甚至可以将所有代码合并到一个文件中。可以使用<DependentOn />
标记实现缩进链接 - 类似于用于XAML文件的代码隐藏cs
的标记。
第二次 - 我已将protected
访问修饰符添加到可绑定属性的getter或setter中。但是当应用XAMLC时看起来它会失败。我已经更新了github示例中的代码。
第三次 - 是的,可以使用从View
(可能是BoxView
或Frame
)继承的任何控件
由于这些命令(可绑定属性)的类型为ICommand
,因此要Execute
- 您需要传入一个参数。如果命令不需要参数 - 您可以使用null
。
推荐用法:
if(timerView.StopTimerCommand.CanExecute(null))
timerView.StopTimerCommand.Execute(null);