前几天我遇到了一个问题。我已经找到了为什么会这样,但是我从来没有遇到过这样的问题,所以我不知道如何解决它。
我有一个应用程序,其中 DashboardView(主视图)中的 DispatcherTimer 在 DashboardViewModel 中启动。当 Timer 计时,我们从数据库中获取数据,这个列表是 View 和 ViewModel 之间的数据绑定(bind)。当有新数据导致数据库发生变化时,会播放声音。
用户可以转到其他 View 。当用户返回 DashboardView 时,会再次创建 DashboardViewModel 和 DispatcherTimer。
现在有 2 个计时器,它们都触发了 Tick 事件,为用户创造了一个令人困惑的场景。
这是我对应用程序现在发生的情况的观察:
我的计时器每分钟都在滴答作响。当我启动应用程序时,DashboardView #1 打开。 DashboardViewModel #1 启动,DispatcherTimer #1 也启动。
我切换到不同的 View ,并更新数据(一封新电子邮件),因此当计时器滴答作响时,仪表板 View 中的列表将更改并播放声音。
当 Timer #1 为 30 秒时,我切换到 DashboardView,它是新创建的,因此创建了 View&ViewModel&Timer #2。
1 分钟后,Timer #1 滴答作响,有新数据,因此它会更新数据库并播放声音,但 View 中的列表没有更新。
我认为这是因为 View #2 显示在#1 之上。我知道,否则我会看到一个叠加层,说它令人耳目一新。
View #2 数据绑定(bind)到 ViewModel #2。 Timer #1 更新了 ViewModel #1,所以更改不会显示,因为我们看不到 View #1,因为它被 View #2 替换/重叠。
1 分 30 秒后,Timer #2 滴答作响,从 DB 获取数据,不播放声音,因为 DB 已由 Timer #1 更新,并以新状态显示数据。
(我希望这是有道理的)
所以,TLDR:有 2 个计时器正在运行,而只有 1 个应该处于事件状态(我认为是最新的)。
我怎样才能做到这一点?
这是我现在拥有的 DashboardViewModel 的(部分):
namespace QRM.ViewModel
{
class DashboardListViewModel : INotifyPropertyChanged
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
DBServer dbServer = new DBServer();
#region Constructor
public DashboardListViewModel()
{
log.Info("Dashboard Initializing - Starting...");
MyObservableCollection<View_server> listDashboard = new MyObservableCollection<View_server>();
ListDashboard = dbServer.ReadDashboard();
listBoxCommand = new RelayCommand(() => SelectionHasChanged());
// Refresh to get all new emails, errors, etc.
GetListDashboard();
IsRefreshing = Visibility.Collapsed;
// Make a timer to renew the data in the Dashboard automatically.
DispatcherTimer timer = new DispatcherTimer();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = Properties.Settings.Default.Timer_interval; // hours, minutes, seconds.
timer.Start();
//Receive the Notification sent after DashboardDetailsViewModel has handled the button commands, and call a respond method for the List.
App.Messenger.Register("RefreshServers", (Action)(() => GetListDashboard()));
App.Messenger.Register("ClearSelection", (Action)(() => SelectedServer = null));
App.Messenger.Register("ErrorSolved", (Action)(() => KeepSelection(selectedServer)));
App.Messenger.Register("WarningSound", (Action)(() => HasNewError = true));
log.Info("Dashboard Initializing - Done.");
}
#endregion
#region Get list dashboard
private void GetListDashboard()
{
HasNewError = false;
log.Info("Dashboard - Checking for Email...");
// The old Outlook class and methods
//EmailManager checkMail = new EmailManager();
//checkMail.GetEmail();
// First, check for mail.
IMAPManager checkMail = new IMAPManager();
checkMail.GetEmail();
log.Info("Dashboard - Checking for linked Errors...");
// Check if the emails have Errors linked to them. If not, add the Error from the Email to the DB
ErrorManager checkError = new ErrorManager();
checkError.GetNewErrors();
log.Info("Dashboard List - Starting...");
// Load the dashboard.
ListDashboard = dbServer.ReadDashboard();
System.Diagnostics.Debug.WriteLine("REFRESHED THE DASHBOARD");
log.Info("Dashboard List - Done.");
}
private void KeepSelection(View_server keepSelection)
{
GetListDashboard();
SelectedServer = keepSelection;
SelectionHasChanged();
}
#endregion
#region Timer
//This method runs every time the timer ticks.
private async void timer_Tick(object sender, EventArgs e)
{
log.Info("Dashboard - Refreshing...");
System.Diagnostics.Debug.WriteLine(">>Timer tick");
IsRefreshing = Visibility.Visible;
// To make sure the overlay is visible to the user, let it be on screen for at least a second (2x half a second)
await Task.Delay(500);
if (selectedServer != null)
{
KeepSelection(selectedServer);
}
else
{
GetListDashboard();
}
// 2nd half second.
await Task.Delay(500);
IsRefreshing = Visibility.Collapsed;
if (hasNewError == true)
{
System.Diagnostics.Debug.WriteLine("List has new error");
PlayWarningSound();
HasNewError = false;
}
else
{
System.Diagnostics.Debug.WriteLine("List has no new error");
HasNewError = false;
}
System.Diagnostics.Debug.WriteLine(">>End timer");
log.Info("Dashboard - Refreshed.");
}
#endregion
}
}
最佳答案
这里有几个问题。让我们先从最基本的开始:
清理
当DashboardListViewModel
被处置或关闭,您需要断开您的 DispatcherTimer.Tick
事件处理程序,调用 .Stop()
然后调用.Finalize()
. MSDN .这将确保您的 System.Windows.Threading.DispatcherTimer
已妥善清理。
异步/等待和事件处理程序
此外,DispatcherTimer.Tick
事件处理程序定义为 async void
.这是 async
的错误用法关键词。而是使用这个:
private void timer_Tick(object sender, EventArgs e)
{
log.Info("Dashboard - Refreshing...");
System.Diagnostics.Debug.WriteLine(">>Timer tick");
IsRefreshing = Visibility.Visible;
// To make sure the overlay is visible to the user, let it be on screen for at least a second (2x half a second)
Thread.Sleep(500);
if (selectedServer != null)
{
KeepSelection(selectedServer);
}
else
{
GetListDashboard();
}
// 2nd half second.
Thread.Sleep(500);
IsRefreshing = Visibility.Collapsed;
if (hasNewError == true)
{
System.Diagnostics.Debug.WriteLine("List has new error");
PlayWarningSound();
HasNewError = false;
}
else
{
System.Diagnostics.Debug.WriteLine("List has no new error");
HasNewError = false;
}
System.Diagnostics.Debug.WriteLine(">>End timer");
log.Info("Dashboard - Refreshed.");
}
我通常从不建议使用
Thread.Sleep
但是由于您已经在线程计时器的上下文中,这是有道理的。最后一个问题
你确定
App.Messenger.Register
可以多次调用,因为每次实例化 View 模型时都会发生这种情况?我会想象这将是你只想做一次的事情,在 static
语境。
关于c# - 新的 DispatcherTimer 与旧的一起创建,只有新的应该运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36864779/