WPF技术的主要特点是数据驱动UI,所以在使用WPF技术开发的过程中是以数据为核心的,WPF提供了数据绑定机制,当数据发生变化时,WPF会自动发出通知去更新UI。
恰当的模式可以让我们轻松达到“高内聚低耦合”,MVVM就是为WPF量身定做的,该模式充分利用了WPF的数据绑定机制,最大限度地降低了XAML和CS文件的耦合度,即UI显示和逻辑代码的耦合度,如需更换界面时,逻辑代码修改很少,甚至不用修改。与WinForm开发相比,我们一般在后置代码中会使用控件的名字来操作控件的属性来更新UI,而在WPF中通常是数据绑定来更新UI。在响应用户操作上,WinForm是通过控件的事件来处理,而WPF可以使用命令绑定的方式来处理,耦合度将降低。
我们可以通过下图来理解MVVM模式:
View,UI界面,即XAML实现的页面,负责与用户交互,接收用户输入,把数据展现给用户。
ViewModel,一个C# 类,是View的抽象,负责收集需要绑定的数据和命令,帮助View和Model之间的信息转换,将View的Command传送到Model,聚合Model对象,通过View类的DataContent属性绑定到View,同时也可以处理一些UI逻辑。
Model,数据访问层,就是系统中的对象,可包含属性和行为。
一般,View对应一个ViewModel,ViewModel可以聚合N个Model,ViewModel可以对应多个View,Model不知道View和ViewModel的存在。
View与ViewModel连接可通过下面的方式:
(1)Binding Data:实现数据的传递;
(2)Command:实现操作的调用;
(3)AttachBehavior:实现控件加载过程中的操作;
示例讲解:
一、Model
class ButtonInfo { public string Content { get; set; } }
class DownLoadFileInfo { public string url = ""; public string fileName = ""; public DownLoadFileInfo(string _url, string _fileName) { url = _url; fileName = _fileName; } }
class ProgressBarInfo { public long pbCurrentMaxLength { get; set; } public long pbCurrentLength { get; set; } public long pbTotalMaxLength { get; set; } public long pbTotalLength { get; set; } public ProgressBarInfo() { } public ProgressBarInfo(long pbCurrentMaxLength, long pbCurrentLength, long pbTotalMaxLength, long pbTotalLength) { this.pbCurrentMaxLength = pbCurrentMaxLength; this.pbCurrentLength = pbCurrentLength; this.pbTotalLength = pbTotalLength; this.pbTotalMaxLength = pbTotalMaxLength; } }
二、View
<Window x:Class="AutoUpdate_MVVM.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="201" Width="505"> <Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="GESBrushes.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources> <Grid Background="{StaticResource SolidBrushBackground}"> <Button Content="{Binding Button.Content,Mode=TwoWay}" Command="{Binding Pause}" Name="btnPause" HorizontalAlignment="Left" Margin="108,113,0,0" VerticalAlignment="Top" Width="75"/> <Button Content="关闭" Command="{Binding Close}" Name="btnClose" HorizontalAlignment="Left" Margin="251,113,0,0" VerticalAlignment="Top" Width="75" /> <ProgressBar Value="{Binding ProgressBar.pbCurrentLength,Mode=TwoWay}" Maximum="{Binding ProgressBar.pbCurrentMaxLength}" Name="pbCurrent" HorizontalAlignment="Left" Height="16" Margin="90,32,0,0" VerticalAlignment="Top" Width="355"/> <ProgressBar Value="{Binding ProgressBar.pbTotalLength,Mode=TwoWay}" Maximum="{Binding ProgressBar.pbTotalMaxLength}" Name="pbTotal" HorizontalAlignment="Left" Height="16" Margin="90,65,0,0" VerticalAlignment="Top" Width="355"/> <Label Content="当前进度:" Foreground="{StaticResource SolidBrushForeground}" Height="28" HorizontalAlignment="Left" Margin="25,25,0,0" Name="label1" VerticalAlignment="Top" FontWeight="Normal" FontStyle="Normal" FontStretch="{Binding}" /> <Label Content="总 进 度:" Foreground="{StaticResource SolidBrushForeground}" Height="28" HorizontalAlignment="Left" Margin="25,59,0,0" Name="label2" VerticalAlignment="Top" /> </Grid> </Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new DownLoadFile();
}
}
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <SolidColorBrush x:Key="SolidBrushBackground" Color="#FF3A9692"/> </ResourceDictionary>
三、ViewModel
(1)DownLoadFile类,一个实现INotifyPropertyChanged接口的类,目的是绑定数据属性。WPF中实现这个接口的类的属性成员才具有通知UI的能力。
class DownLoadFile : INotifyPropertyChanged,IDisposable { public event PropertyChangedEventHandler PropertyChanged; #region [Object] ManualResetEvent _pauseEvent = new ManualResetEvent(true); List<DownLoadFileInfo> _listFileInfo = new List<DownLoadFileInfo>(); List<string> _listUrl = new List<string>(); readonly int MAX_BUFFER_SIZE = 512; long totalCurrentLength = 0; long totalLength = 0; ProgressBarInfo _ProgressBarInfo; ButtonInfo _ButtonInfo; HttpWebRequest myrq; HttpWebResponse myrp; #endregion #region [Property] public bool IsFinish { get; set; } private ProgressBarInfo _ProgressBar; public ProgressBarInfo ProgressBar { get { return _ProgressBar; } set { _ProgressBar = value; if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("ProgressBar")); ; } } } private ButtonInfo _Button; public ButtonInfo Button { get { return _Button; } set { _Button = value; if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("Button")); } } } public ICommand Pause { get { return new PauseCommand(this); } } public ICommand Close { get { return new CloseCommand(this); } } #endregion #region DownLoadFile public DownLoadFile() { _ProgressBarInfo = new ProgressBarInfo(); _ButtonInfo = new ButtonInfo(); _ButtonInfo.Content = "暂停"; Button = _ButtonInfo; //注意此次的赋值 _listUrl.Add(@"http://127.0.0.1/孙晓林周报(2014-12-11)1.txt"); _listUrl.Add(@"http://127.0.0.1/孙晓林周报(2014-12-11)2.txt"); for (int i = 0; i < _listUrl.Count; i++) { string url = _listUrl[i]; string[] fileNames = url.Split('/'); if (fileNames.Length > 0) { string fileName = fileNames[fileNames.Length - 1]; string fileFullName = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "\" + fileName;//文件保存路径 DownLoadFileInfo fileInfo = new DownLoadFileInfo(_listUrl[i], fileFullName); _listFileInfo.Add(fileInfo); } } for (int i = 0; i < _listFileInfo.Count; i++) { HttpWebRequest myrq = (HttpWebRequest)HttpWebRequest.Create(_listFileInfo[i].url); HttpWebResponse myrp = (HttpWebResponse)myrq.GetResponse(); totalLength += myrp.ContentLength; } //下载文件 Thread newThread = new Thread(new ThreadStart(PerformDownloading)); newThread.Start(); } #endregion #region PerformDownloading /// <summary> /// 下载文件 /// </summary> private void PerformDownloading() { try { if (_listFileInfo != null) { for (int i = 0; i < _listFileInfo.Count; i++) { string url = _listFileInfo[i].url; string fileName = _listFileInfo[i].fileName; _ProgressBarInfo.pbCurrentLength = 0; //System.Net.ServicePointManager.DefaultConnectionLimit = 50; System.GC.Collect(); myrq = (HttpWebRequest)HttpWebRequest.Create(url); myrq.KeepAlive = false; myrp = (HttpWebResponse)myrq.GetResponse(); _ProgressBarInfo.pbCurrentMaxLength = myrp.ContentLength; _ProgressBarInfo.pbTotalMaxLength = totalLength; System.IO.Stream st = myrp.GetResponseStream(); System.IO.Stream so = new System.IO.FileStream(fileName, System.IO.FileMode.Create); long totalDownloadedByte = 0; byte[] buffer = new byte[MAX_BUFFER_SIZE]; int osize = 0; while (true) { _pauseEvent.WaitOne();//阻止当前线程,直到当前 WaitHandle 收到信号。 osize = st.Read(buffer, 0, MAX_BUFFER_SIZE); totalDownloadedByte += osize; totalCurrentLength += osize; _ProgressBarInfo.pbCurrentLength = totalDownloadedByte; _ProgressBarInfo.pbTotalLength = totalCurrentLength; ProgressBar = _ProgressBarInfo; Thread.Sleep(1); if (osize == 0) { break; } so.Write(buffer, 0, osize); } so.Close(); st.Close(); CloseHttpWebObject(); } IsFinish = true; } } catch (System.Exception ex) { MessageBox.Show(ex.Message); } } #endregion #region PauseDownLoad public void PauseDownLoad() { if (Button.Content == "暂停") { _ButtonInfo.Content = "继续"; Button = _ButtonInfo; _pauseEvent.Reset(); } else { _ButtonInfo.Content = "暂停"; Button = _ButtonInfo; _pauseEvent.Set(); } } #endregion #region IDisposable 成员 public void Dispose() { _pauseEvent.Close();//释放由当前 WaitHandle 持有的所有资源。 for (int i = 0; i < _listFileInfo.Count; i++) { _listFileInfo[i] = null; } CloseHttpWebObject(); Application.Current.Shutdown(); } private void CloseHttpWebObject() { if (myrq != null) { myrq.Abort(); } if (myrp != null) { myrp.Close(); } } #endregion }
(2)CloseCommand类和PauseCommand类,实现ICommand接口的类,目的是绑定命令属性。WPF中实现ICommand接口的类才能作为命令绑定到UI。
class CloseCommand:ICommand { private DownLoadFile _DownLoadFile; public CloseCommand(DownLoadFile downLoadFile) { _DownLoadFile = downLoadFile; } #region Achieve Items public bool CanExecute(object parameter)//定义用于确定此命令是否可以在当前状态下执行的方法,如果可以执行此命令,返回true,否则返回false。 { return true; } public event EventHandler CanExecuteChanged;//当出现影响是否执行该命令的更改时发生 public void Execute(object parameter)//定义在调用此命令时调用的方法 { _DownLoadFile.Dispose(); } #endregion }
class PauseCommand : ICommand { private DownLoadFile _DownLoadFile; public PauseCommand(DownLoadFile downLoadFile) { _DownLoadFile = downLoadFile; } public bool CanExecute(object parameter) { if (_DownLoadFile.IsFinish) { return false; } else { return true; } } public event EventHandler CanExecuteChanged; public void Execute(object parameter) { _DownLoadFile.PauseDownLoad(); } }
二、