如果我做错了,我很抱歉,我已经20年没有接触过c#了,我是MVVM的新手。无论如何,我正在使用Avalonia(据我所知,它与WPF几乎相同,但跨平台),我在UI中显示时钟时遇到了一些麻烦。我有以下代码在我的MainWindow.axaml.
<Border Classes="Footer" Grid.Row="2" Grid.Column="0">
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Text="{Binding NextDownloadCycle}"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Text="{Binding CurrentTime}"/>
</StackPanel>
</DockPanel>
</Border>
在我的MainWindowViewModel文件中,我有以下代码,应该使此工作。
public event PropertyChangedEventHandler? PropertyChanged;
public string CurrentTime {
get { return _currentTime; }
set
{
_currentTime = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
Debug.WriteLine(value);
}
}
private string _nextDownloadCycle = "Next crawl session at: (in 30m)";
public string NextDownloadCycle {
get { return _nextDownloadCycle; }
set
{
_nextDownloadCycle = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NextDownloadCycle)));
Debug.WriteLine(value);
}
}
private DateTime _nextCycle = DateTime.Now.AddHours(1);
public MainWindowViewModel()
{
StartClock();
}
private void StartClock()
{
new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Normal, TimerTick).Start();
}
void TimerTick(object sender, EventArgs e)
{
var now = DateTime.Now;
if (now >= _nextCycle)
{
_nextCycle = DateTime.Now.AddHours(1);
StartCrawling();
}
TimeSpan ts = _nextCycle - now;
NextDownloadCycle = String.Format(
"Next crawl session at: {0} (in {1}m{2}s)",
_nextCycle.ToString("HH:mm:ss"),
Math.Round(ts.TotalMinutes),
ts.Seconds
);
CurrentTime = now.ToString("HH:mm:ss");
}
我试着把PropertyChanged?调用内部的Dispatcher.UIThread.InvokeAsync(() => )
思维,可能会以某种方式修复它(我是新的,你可能会告诉)无济于事。我在调试控制台中得到的输出大部分是我所期望的,例如
Next crawl session at: 00:22:03 (in 60m32s)
23:22:31
有时事情会有点不对劲,我得到两次提到一秒,然后是2秒的间隔,就像下面这样,我猜这只是由于四舍五入的缘故。
Next crawl session at: 00:47:00 (in 60m48s)
23:47:11
Next crawl session at: 00:47:00 (in 60m48s)
23:47:12
Next crawl session at: 00:47:00 (in 60m46s)
无论如何,我的主要问题是为什么不更新UI?我需要改变什么,我做错了什么?最终使其工作如下。首先,我在模型文件夹中创建了一个单独的Clock类,它扩展了INotifyPropertyChanged,并将格式和计时器逻辑移到了其中。
public class Clock : INotifyPropertyChanged
{
private DispatcherTimer _disTimer = new DispatcherTimer();
public event PropertyChangedEventHandler? PropertyChanged;
public EventHandler<DateTime> OnEveryHour = (s , e) => { };
public string CurrentTime { get; set; } = "Current Time";
public string NextDownloadCycle { get; set; } = "Downloading in: ";
private DateTime _nextCycle = DateTime.Now.AddHours(1);
public Clock()
{
_disTimer.Interval = TimeSpan.FromSeconds(1);
_disTimer.Tick += DispatcherTimer_Tick;
_disTimer.Start();
}
private void DispatcherTimer_Tick(object? sender, EventArgs e)
{
var now = DateTime.Now;
if (now >= _nextCycle)
{
_nextCycle = DateTime.Now.AddHours(1);
OnEveryHour(this, now);
}
TimeSpan ts = _nextCycle - now;
NextDownloadCycle = String.Format(
"Next crawl session at {0}(in {1}m{2}s)",
_nextCycle.ToString("HH:mm:ss"),
Math.Round(ts.TotalMinutes) - 1,
ts.Seconds
);
CurrentTime = now.ToString("HH:mm:ss");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentTime)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NextDownloadCycle)));
}
}
然后在MainWindowViewModel我创建我的时钟实例public Clock Clock { get; set; } = new Clock();
在xaml中调用它,如下所示:
<Border Classes="Footer" Grid.Row="2" Grid.Column="0">
<DockPanel HorizontalAlignment="Stretch" DataContext="{Binding Clock}">
<TextBlock Text="{Binding NextDownloadCycle}"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Text="{Binding CurrentTime}"/>
</StackPanel>
</DockPanel>
</Border>
我将简单地订阅MainWindowViewModel
中的OnEveryHour
来执行StartCrawling()
,一切都应该像我想要的那样工作。(额外的好处,代码看起来[c]精简)