WPF之MVVM模式讲解

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; }
    }
ButtonInfo
    class DownLoadFileInfo
    {
        public string url = "";
        public string fileName = "";

        public DownLoadFileInfo(string _url, string _fileName)
        {
            url = _url;
            fileName = _fileName;
        }
    }
DownLoadFileInfo
    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;
        }
    }
ProgressBarInfo

二、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>
MainWindow.xaml
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new DownLoadFile();
        }
    }
MainWindow.xaml.cs
<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>
GESBrushes.xaml

三、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
    }
CloseCommand
 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();
        }
    }
PauseCommand

二、

原文地址:https://www.cnblogs.com/SunXiaoLin/p/4221117.html