1.MVP模式
首先简单介绍下MVP设计模式
这是个草图 简单来说Model相当于专门管数据的,比如数据的增删改查之类的,View就是管理UI显示的,为了不让Model和View直接产生交互,在它们之间创建了一个Presenter,让它来间接的让Model和View产生交互。
那为什么不能让Model和View直接产生交互呢?因为在一些大型的项目里面,Model层和View层是可以分给两拨人进行同时开发的,这样的话就提高了效率 后面维护和测试也会容易很多。
下面我将以MVP设计模式来开发一个简单的登录界面。我这里是使用的winform。
2.登录界面Demo
2.1.Model层
2.1.1 数据的准备
首先来准备数据,我这里使用的是SQL Server,使用到了Microsoft SQL Server Management Studio可视化界面对数据库进行一个简单的编辑。
首先创建一个数据库TestDB,然后再该数据库下新建一个表tb_User,表的内容如下:
这个表也只有两列UserName和Password,数据类型都是nvarchar(50),同时将UserName设置为主键,防止用户名重复。然后我准备了两组数据,在上面也可以看到。
2.2.2 代码的编写
开发环境我这里使用的是Vs 2019 新建一个Windows窗体应用,然后把它自带的一个窗口Form1给删除掉 再修改一下Program.cs 把报错的那一行先注释掉。
然后在项目下新建一个Model文件夹,在此文件夹下在新建一个文件夹_Repositories。当然你也可以不建文件夹,但是为了方便管理还是推荐建文件夹。然后在Model文件夹下新建一个cs文件,命名为UserModel,内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVPDemo.Model
{
public class UserModel
{
private string userName;
private string password;
public string UserName { get => userName; set => userName = value; }
public string Password { get => password; set => password = value; }
}
}
这个脚本内容很简单,就是根据数据库里面表的列来建立成员变量和属性。
再新建一个接口文件 IUserRepositories,内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVPDemo.Model
{
public interface IUserRepositories
{
bool Login(string UserName,string Password);
}
}
此接口包含一个方法登录方法
然后在Model的子文件夹_Repositories新建两个cs文件,BaseRepositories和UserRepositories,它们的内容如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVPDemo.Model._Repositories
{
public class BaseRepositories
{
protected string connectionString;
}
}
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVPDemo.Model._Repositories
{
public class UserRepositories : BaseRepositories, IUserRepositories
{
public UserRepositories(string connectionString)
{
this.connectionString = connectionString;
}
public bool Login(string UserName, string Password)
{
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand())
{
connection.Open();
command.Connection = connection;
command.CommandText = "SELECT COUNT(*) FROM tb_User WHERE UserName = @UserName AND Password = @Password";
command.Parameters.Add("@UserName", SqlDbType.NVarChar).Value = UserName;
command.Parameters.Add("@Password", SqlDbType.NVarChar).Value = Password;
int result = (int)command.ExecuteScalar();
return result > 0;
}
}
}
}
首先是 BaseRepositories 这个类中有一个成员connectionString,这个字符串就是帮我们连接到具体的数据库中的。
再来看UserRepositories,这个类就是Model层的核心代码了,下面来解释下这段代码。
他继承自BaseRepositories类和IUserRepositories接口,首先是它的构造函数,在这个构造函数中就是让外界在实例化它的时候传一个字符串进来,这个字符串用来和指定的数据库完成连接。
然后就是重写了Login方法,这个方法将传入的用户名字符串和密码字符串和数据库中的用户数据表进行比对,最后使用ExecuteScalar方法进行查询,由于我们将用户数据表的UserName设置为主键,所以在使用ExecuteScalar方法时,如果比对成功,那么就只会有一条数据,否则就没有数据,表明账号不对或者密码错误。最后根据数量进行布尔值的返回。
这里就完成了一个数据的处理。主要是数据的查询。可以看到,对于数据的处理,完全没有和任何UI代码产生交互,这也是我在前文说为什么使用MVP可以让两拨人同时进行开发。
此外,大家可能会有疑问,为什么我这里要使用接口呢?代码其实很简单,不使用接口不是也可以完成吗?虽然不是强制性的,但是这里还是建议使用接口,我这里只有一个登录界面,如果后面登录界面多了,都可以使用这个接口,这样代码维护和测试起来也会变得更加方便。
2.2 View层
view层也就是UI层 是给人看和操作的。
2.2.1 UI界面的拼接
首先新建一个View文件夹,在此文件夹下新建一个窗体(Windows窗体)。然后打开窗体的设计界面,简单拼接一个登录窗口。
我这个窗口很简单,也没有做窗口的自适应什么的,窗口的设计大家可以去学习winform,这里只做演示。对账号的文本输入框重命名为txtUserName,对密码后面的输入框命名为txtPassword。登录按钮命名为btnLogin 忘记密码命名为btnSearch。
2.2.2 脚本的编写
首先还是来创建一个接口ILoginView
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVPDemo.View
{
public interface ILoginView
{
string UserName { get; set; }
string Password { get; set; }
string Message { get; set; }
event EventHandler LoginEvent;
event EventHandler SearchEvent;
void Show();
}
}
这个接口相对复杂一丝。首先是两个属性UserName和Password,这两个属性是用来和界面的输入框文本进行关联的。
然后是Message,这个是用来改变提示信息的,比如账号输入错误,那么可以把这个属性改变为账号或者密码错误。 然后两个事件LoginEvent和SearchEvent,这两个主要是用来处理点击登录和忘记密码按钮的。
然后是LoginView.cs,这里说一下如何打开这个脚本,右键界面,点击查看代码就可以了,不需要额外创建,这个脚本在创建winfrom时就自动创建了。下面来看这个脚本的内容
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MVPDemo.View
{
public partial class LoginView : Form,ILoginView
{
private string message;
public string UserName { get => txtUserName.Text ; set => txtUserName.Text = value; }
public string Password { get => txtPassword.Text; set => txtPassword.Text = value; }
public string Message { get => message; set => message = value; }
public event EventHandler LoginEvent;
public event EventHandler SearchEvent;
public LoginView()
{
InitializeComponent();
AssociateAndRaiseViewEvents();
}
private void AssociateAndRaiseViewEvents()
{
btnLogin.Click += delegate
{
LoginEvent?.Invoke(this,EventArgs.Empty);
MessageBox.Show(message);
};
btnSearch.Click += delegate
{
SearchEvent?.Invoke(this, EventArgs.Empty);
};
}
}
}
首先它实现了 ILoginView接口,然后将两个文本框的内容和属性相关联。最后是额外定义了一个message和相对应的属性。然后在AssociateAndRaiseViewEvents中将两个按钮的点击事件和接口中的两个事件绑定起来了。这里先说登录按钮点击事件绑定,触发点击事件,然后显示一个信息。
OK,这样我们也完成了View层的编写,你可以看到view和Model一样也没有和其他层尤其是Model层进行交互。
2.3 Presenter层
有了view层和Model层,二者之间丝毫没有发生交互,因为交互交给了接下来要完成的Presenter层。
创建文件夹Presenter,然后创建一个Presenter脚本,内容如下:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MVPDemo.Model;
using MVPDemo.Model._Repositories;
using MVPDemo.View;
namespace MVPDemo.Presenter
{
public class LoginPresenter
{
private IUserRepositories repositories;
private ILoginView view;
public LoginPresenter(IUserRepositories repositories, ILoginView view)
{
this.repositories = repositories;
this.view = view;
this.view.LoginEvent += Login;
this.view.SearchEvent += Search;
}
private void Search(object sender, EventArgs e)
{
ISearchView view = SearchView.Instance();
ISearchRepositories repositories = new SearchRepositories(ConfigurationManager.ConnectionStrings["MVPDemo.Properties.Settings.SqlConnection"].ConnectionString);
new SearchPresenter(repositories, view);
}
private void Login(object sender, EventArgs e)
{
if (repositories.Login(view.UserName, view.Password)) view.Message = "登录成功!";
else view.Message = "登录失败!";
}
}
}
首先声明了两个成员变量,这里是以接口的形式声明的。
然后在构造函数中将view层的两个事件和两个函数绑定了。这里先不看 Search函数,先来看Login函数,在这个函数中完成对于view和Model层的交互,我们使用IUserRepositories接口(Model层)的Login函数来判断ILoginView的两个属性(View层)是否和数据库中的匹配,还记得ILoginView的两个属性UserName和Password在LoginView中我们将它们和两个文本框内容绑定了吗。由于Login函数返回的是布尔值,true代表账号密码正确,false代表错误。利用这个值,我们在去设置view的Message属性,同样的在LoginView中我们将Message属性和message绑定了。
上面就是交互过程,这样Presenter也完成了。
2.4 整个项目的运行
在Program.cs中我们完成整个项目的运行。
首先完成一步,打开Properties的设置,如下图
先点击1 在点击2 ,名称你可以自己起,然后点击3 ,然后这样设置
如果你的数据库在同一台电脑上,服务器名填写(local) ,身份验证按照你自己连接数据库时选择,数据库名按照你自己的写,最后测试连接,会出现测试连接成功的提示。
这样做实际上就是方便系统直接生成连接数据库的字符串,后面也可以直接用。最后来看Program.cs代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using MVPDemo.Model;
using MVPDemo.Model._Repositories;
using MVPDemo.View;
using MVPDemo.Presenter;
using System.Configuration;
namespace MVPDemo
{
static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
string sqlConnectionString = ConfigurationManager.ConnectionStrings["MVPDemo.Properties.Settings.SqlConnection"].ConnectionString;
ILoginView view = new LoginView();
IUserRepositories repositories = new UserRepositories(sqlConnectionString);
new LoginPresenter(repositories, view);
Application.Run((Form)view);
}
}
}
那个sqlConnectionString就是我们前面设置的,ConfigurationManager处如果报错了,那么在项目的引用下添加一个System.Configuration就可以了。
最后的效果如下:
如果密码错误,那么提示的信息就是登录失败了,这里就不演示了。
结语
剩下一个忘记密码你可以自己按照这种思路完成,我会把项目传到github上,忘记密码的部分可能会和登录有些不一样,多用了一个部分BindingSource,后面有时间我会完成这一部分的解释。