Silverlight的设计模式(MVVM)初体验

这几天看了一些关于Silverlight的设计模式相关的文章,主流观点是:Silverlight + WCF + MVVM + Prism,而这几个都不太了解,汗啊~

没关系,“没吃过猪肉还没见过猪跑吗”,既然我们自己的项目中没有用到,那就找一些别人写好的吧,边看边学,边学边用,人总是要不断前进的,俗话说,学习如逆水行舟,不进则退,得继续努力了!

还是使用之前《Silverlight之路》中使用的项目,先以登录页面为入口,尝试使用MVVM进行开发,先建好对应的目录(当然,这个不是必须的)

 

把之前的登录窗口放到对于View目录中,修改好对应的命名空间,这样,我们的View就差不多了,接下来看看我们的Model,它应该是什么呢?

在我们后台的WCF中,用户登录会到TB_Common_User表中进行验证,于是在我们的实体框架中就有了一个对应的实体类型,那么它是不是就是我们的Model呢?说实话,刚接触的时候我一直以为他们是一回事。但显然,他们不是同一个概念。做为实体的Model,它只是一个数据类型,方便我们在项目中使用,它本身不存在任何逻辑;而做为行为的Model,也就是我们LoginModel,它的责任就要大的多,它负责为ViewModel提供数据,并处理来自ViewModel中的反馈与请求,可以说,它是整个功能的核心要素,想想看,不管是MVC、MVP还是MVVM,为什么它总在第一位呢?呵呵。

知道了Model,那么VM又是什么呢?一个简单的理解:理论上讲,对于后台程序员来说,并不知道View的存在!因为在开发的过程中,View可能会由美工与前端程序员负责,而后台的业务逻辑等由后台程序员负责,为了并行开发和避免相互依赖,需要一个中间件来连接前后台,这就是ViewModel。因此,对于我们来说,ViewModel就是我们的View。至于它如何与View进行关联,这就是SL与WPF中的强大的Binding系统负责的事情了。

由此可以想像,为了“不知道”View存在,就需要在ViewModel中建立View需要的所有数据,我们的目的就是要“以数据驱动界面”,可以设想一下,界面上任何需要后台控制的属性都需要在ViewModel中有与之对应的属性存在,事件也是一样,可以通过命令进行绑定(不过似乎SL中对命令的支持有限,只有一系列按钮可以支持)。

有了上面的概念,回到我们的需求中,分析一下登录模块需要的功能流程:输入,验证输入,登录请求,登录验证,返回登录结果,下一步操作(成功则跳转,失败则提示)。对号入座:输入(View),验证输入(ViewModel),登录请求(Model),登录验证(WCF),返回登录结果(Model),下一步操作(ViewModel),责任分清之后可以进行实现了,不过这里有必要提一下验证输入这一块,在SL是可没有类似Web中那一系列验证控件(Tookit中有,但原理完全不同),SL中的验证可以通过IDataErrorInfo接口来实现,这个一会再说,先看一下代码吧,有时代码比文字更能说明问题。

View Code
public class LoginModel

{

WcfServiceClient client
= new WcfServiceClient();



public string UserName { set; get; }

public string PassWord { set; get; }



public LoginModel()

{

client.loginCompleted
+= new EventHandler<loginCompletedEventArgs>(client_loginCompleted);

}



void client_loginCompleted(object sender, loginCompletedEventArgs e)

{

Action
<int> callback = e.UserState as Action<int>;

if (callback != null)

callback(e.Result);

}



public void RequestLogin(Action<int> callback)

{

client.loginAsync(UserName, PassWord, callback);

}

}

LoginModel类包括两个属性与一个方法,RequestLogin的参数是一个Action,用来在验证登录后通知ViewModel做处理。再看看ViewModel的代码

View Code
public class LoginViewModel : INotifyPropertyChanged, IDataErrorInfo

{

public event PropertyChangedEventHandler PropertyChanged;



private Model.LoginModel lm = new Model.LoginModel();



public string UserName

{

get { return lm.UserName; }

set

{

lm.UserName
= value;

if (PropertyChanged != null)

PropertyChanged(
this, new PropertyChangedEventArgs("UserName"));

}

}



public string Password

{

get { return lm.PassWord; }

set

{

lm.PassWord
= value;

if (PropertyChanged != null)

PropertyChanged(
this, new PropertyChangedEventArgs("Password"));

}

}



private ICommand _RequestLoginCommand;



public ICommand RequestLoginCommand

{

get

{

if (_RequestLoginCommand == null)

_RequestLoginCommand
= new Commands.DelegateCommand(RequestLogin);

return _RequestLoginCommand;

}

}



public void RequestLogin(object obj)

{

lm.RequestLogin(RequestLoginCallBack);

}



private void RequestLoginCallBack(int loginstate)

{

if (loginstate == 1)

{

View.Login l
= App.Current.RootVisual as View.Login;

l.Content
= new MainPage();

}

}



#region IDataErrorInfo 成员

public string Error

{

get { return "出错啦!"; }

}



public string this[string columnName]

{

get

{

string result = null;

int len = 0;



switch (columnName)

{

case "UserName":

// 设置Username属性的验证规则

len
= UserName.Length;

if (len < 2 || len > 10)

{

result
= "Username length must between 2 and 10";

}

break;

case "Password":

// 设置Pwd属性的验证规则

len
= Password.Length;

if (len < 2 || len > 10)

{

result
= "Pwd length must between 2 and 10";

}

break;

}



return result;

}

}

#endregion

}

初一看好像代码很多,其实仔细看看的话,发现代码很简单。不过说实话,使用MVVM开发模式的话,一定会比平常的代码量要大的多,可是哪种模式不是这样呢?开发模式的优势不能简单的用纯代码量来衡量的。

<TextBlock HorizontalAlignment="Right" Margin="0" TextWrapping="Wrap" Text="密码:" Foreground="#FFFDFBFB" Width="{Binding Width, ElementName=textBlock}" TextAlignment="Right" VerticalAlignment="Center"/>

<PasswordBox HorizontalAlignment="Left" Width="{Binding Width, ElementName=txtUserName}" Height="28" VerticalAlignment="Center" Margin="0" x:Name="txtPassWord" Password="{Binding Password, Mode=TwoWay}" />

<Button Content="登录" HorizontalAlignment="Left" Height="27" Margin="0,0,8,0" Width="51" x:Name="btnLogin" Command="{Binding RequestLoginCommand}" />

这个LoginViewModel实现了INotifyPropertyChanged接口,用来双向绑定数据;实现了IDataErrorInfo接口用来验证输入(关于这两个接口的说明,请参考MSDN或其它详细说明)。这样,我们的VM就建立完成了,剩下的工作只要在view进行绑定就可以了,绑定如下

在初始化时实例化一个LoginViewModel并赋值到View的DataContext就完成了。

ViewModel.LoginViewModel lvm = new ViewModel.LoginViewModel();

this.DataContext = lvm;

实现效果如

  

以上就是登录功能的MVVM实现。代码很好理解,最主要的工作其实就是分清责任,有两个基本思想,就是

1、  Model是功能的核心,负责业务逻辑的实现。

2、  ViewModel负责为View提供数据,对Model来说,它就是所有的前台。

这就是我对MVVM的理解了,不知道是否正确,欢迎各个大牛指点拍砖!

最后,这里有两个小问题,当我们第一次打个这个窗口时,我们希望用户名输入框获得焦点,但默认情况下不行,需要做一些小处理,即在Load事件中设置一下

void Login_Loaded(object sender, RoutedEventArgs e)

{

HtmlPage.Plugin.Focus();

this.txtUserName.Focus();

}

然后,当载入完成后,我们还没有输入时,因为不满足数据验证条件,它也会在加载完就给出错误提示,这也不太好,至少你得先让我输入之后再验证吧。这里我找了一个取巧的办法,我发现Click事件会在绑定的Command执行前被触发,因此,我把绑定的动作放到第一次点击登录按钮时,如

void btnLogin_Click(object sender, RoutedEventArgs e)

{

if (this.DataContext == null)

{

ViewModel.LoginViewModel lvm
= new ViewModel.LoginViewModel();

lvm.UserName
= this.txtUserName.Text;

lvm.Password
= this.txtPassWord.Password;

this.DataContext = lvm;

}

}

注意,在第一次绑定时,数据是会从数据源流向目标的,也就是说,我们需要在设置DataContext之前把输入的数据先设置到数据源中,否则如果没有lvm.UserName = this.txtUserName.Text;lvm.Password = this.txtPassWord.Password;这两句,在绑定时输入框就会被清空了。

原文地址:https://www.cnblogs.com/meteortent/p/2101182.html