这是我在某个客户那边讲课的时候遇到一个小问题,在ViewModel中创建的一个Timer,并不会被自动停止,即便使用该ViewModel的View已经被关闭了。这个问题的原因在于Timer的特殊工作机制,它是运行在一个独立的工作线程的,除非明确地停止他,或者整个程序关闭了,它才会停止。这一讲中,我通过实例重现了这个问题,然后提供了一个可行的解决方法。
http://www.tudou.com/programs/view/uO4b2j0N4L8/
备注:该范例使用了MvvmLight作为MVVM框架,请自行安装
Model:
using System; using System.Diagnostics; using System.Linq; namespace SilverlightApplicationSample { public class DataService { public static Customer[] GetCustomers() { Debug.WriteLine(string.Format("[{0}]正在调用数据服务",DateTime.Now)); var rnd = new Random(); return Enumerable.Range(1, rnd.Next(100)).Select(x => new Customer() { CompanyName = "Company " + x.ToString() }).ToArray(); } } public class Customer { public string CompanyName { get; set; } } }
ViewModel:
using System; using System.Windows.Threading; using GalaSoft.MvvmLight; namespace SilverlightApplicationSample { /// <summary> /// 使用MVVMLight实现的MVVM ViewModel /// </summary> public class CustomerWindowViewModel : ViewModelBase { /// <summary> /// 这个方法也不会自动调用 /// </summary> public override void Cleanup() { base.Cleanup(); timer.Stop(); } DispatcherTimer timer = null; public CustomerWindowViewModel() { //正常情况下的绑定 //Customers = DataService.GetCustomers(); //使用定时器调用服务 timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromSeconds(1); timer.Tick += (o, a) => { Customers = DataService.GetCustomers(); }; timer.Start(); } private Customer[] _Customers; public Customer[] Customers { get { return _Customers; } set { if (_Customers != value) { _Customers = value; RaisePropertyChanged("Customers"); } } } } }
View:
<controls:ChildWindow x:Class="SilverlightApplicationSample.CustomerWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" Width="400" Height="300" Title="CustomerWindow" xmlns:local="clr-namespace:SilverlightApplicationSample"> <controls:ChildWindow.DataContext> <local:CustomerWindowViewModel></local:CustomerWindowViewModel> </controls:ChildWindow.DataContext> <Grid x:Name="LayoutRoot" Margin="2"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <ListBox ItemsSource="{Binding Customers}" DisplayMemberPath="CompanyName"> </ListBox> <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" /> <Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" /> </Grid> </controls:ChildWindow>
Page:
using System.Windows; using System.Windows.Controls; namespace SilverlightApplicationSample { public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { var window = new CustomerWindow(); window.Closed += (o, a) => { var vm = window.DataContext as CustomerWindowViewModel; vm.Cleanup(); }; window.Show(); } } }