silverlight+ria services+mvvm, 如何根据条件执行或不执行ViewModel的Command?

adodotnet 2014-05-07 06:13:38
有个问题想先问一下,ria services的silverlight的client-side自动生成的类继承自Entity, 而Entity直接或间接实现了似乎通常在ViewModel才能看到的INotifyPropertyChanged, IEditaleObject,等等吧。还提供了OnXXChanging/Changed, ValidateProperty()……等等,也就是看起来不像一个一般意义上的Model那么纯粹。一般在Ria services参与的情况下,应用mvvm模式时是直接把自动生成的这个(继承自Entity)的类当作ViewModel那样使用吧?我是说一般情况下,似乎也没必要用ViewModel'包装'以后再供View使用。

比如我用DataForm添加/修改记录,输入数据有valation error时怎样住址绑定的ViewModel的Command不执行,只有输入数据全部合法时才执行ViewModel的Command(比如它是用于向服务器提交数据的)。
...全文
329 13 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
adodotnet 2014-05-20
  • 打赏
  • 举报
回复
引用 12 楼 adodotnet 的回复:
涉及到MVVM时曾考虑用DataForm的EndEdit事件
DataForm的EndEdit事件 => DataForm的EditEnded事件
adodotnet 2014-05-20
  • 打赏
  • 举报
回复
我原来的想法是用DataForm来增加记录,且不用它自带的点击导航栏上的'+'的方式来增加记录。 用DataForm的好处是它比较rich, 根据Data Annotation来提示数据验证时发生的错误,可以自动调用实现IEditableObject的绑定对象的BeginEdit/CancelEdit/EndEdit等等。 涉及到MVVM时曾考虑用DataForm的EndEdit事件来绑定ViewModel里的SaveCommand(用来增加记录)。后来发现这样子做严重不靠谱。DataForm自带的Commit/Cancel按钮都有可以触发EditEnded事件。 暂时的作法是‘回归’到DataForm自带的那种增加记录的方式。
vbfool 2014-05-12
  • 打赏
  • 举报
回复
确实,把类扩展这事给忘了,主要是以前从来没用过。 这么一来,看来也不一定非要弄个ViewModel来包装了。
adodotnet 2014-05-11
  • 打赏
  • 举报
回复
引用 9 楼 vbfool 的回复:
所以才会把Model作为ViewModel的一个属性来,然后你要修改的部分就直接绑在Model上,毕竟这个Model本身不能扩展的。

如果不使用把Entity的对应字段全部复制一遍的方式来构建ViewModel,那似乎只能用partial class方式‘扩展'Model了。

一种是在服务器端用partial class扩展,然后让RiaServices自动同步到客户端,
另一种是只在客户端用partial class扩展entity.


EnrolmentDay:入学日期
GraduationDay:毕业日期

只有一条记录:



服务器端用partial class方式为Model增加一个Age字段。
在Web项目的Models/Shared下建了一个Student.shared.cs
namespace ╳╳╳.Web.Models
{
using System;
using System.ComponentModel.DataAnnotations;

public partial class Student
{
[Display(Name="年龄", Order=100)]
public int Age
{
get { return DateTime.Now.Year - Birthday.Year; }
}
}
}

因为是share.cs,所以自动复制到silverlight客户端。

顺便我也测试了一下类级别的验证,主要用来验证‘入学日期’不能大于'毕业日期',也是在Web项目里建的,目的是测试它是否复制到Silverlight,并起作用。
namespace ╳╳╳.Web.Models
{
using System.ComponentModel.DataAnnotations;

public class CheckDataValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Student student = value as Student;
return student.GradurationDay > student.EnrolmentDay ? ValidationResult.Success : new ValidationResult("入学日期不能大于毕业日期");
}
}
}


然后就是扩展Silverlight专用的一个字段,显示毕业已经多少天了的DatePassed,在Silverlight项目里添加DatePassed.cs
namespace ╳╳╳.Web.Models
{
public partial class Student
{
//用于显示已经毕业后已经过了几天
[Display(Name = "毕业已经过了(天)", Order = 200)]
public int DatePassed
{
get
{
return (DateTime.Now - GradurationDay).Days;
}
}
}
}



其他:

//Home.xaml.cs
private StudentDomainContext _context = new StudentDomainContext();
public Home()
{
InitializeComponent();

this.Title = ApplicationStrings.HomePageTitle;
this.Loaded += (s, e) =>
{
EntityQuery<Student> q = _context.GetStudentsQuery();
q = from student in q.Take(1)
select student;
_context.Load(q, lo => myDataForm.ItemsSource = lo.Entities, null);
};
}


Home.xaml
<tookit:DataForm x:Name="myDataForm" CurrentItem="{Binding}" AutoCommit="False"  />


最终结果:


其中DataForm字段的标题在Web项目的╳╳╳DomainService.metadata.cs里用[Display]修饰了一下:
[Display(AutoGenerateField = false)]
public int Id { get; set; }

[Display(Name = "姓名", Order = 5)]
public string Name { get; set; }

[Display(Name="生日", Order=10)]
public DateTime Birthday { get; set; }

[Display(Name = "入学日期", Order = 15)]
public DateTime EnrolmentDay { get; set; }

[Display(Name = "毕业日期", Order = 20)]
public DateTime GradurationDay { get; set; }
vbfool 2014-05-10
  • 打赏
  • 举报
回复
引用 8 楼 adodotnet 的回复:
[quote=引用 7 楼 vbfool 的回复:] 我记得这种时候还是靠IEditaleObject的吧
如果View绑定的是经过包装的ViewModel,比如如下情况: View-StudentViewModel-Student(已经默认实现IEditableObject),那么它们各自在DataForm上的表现如何,我做了一下测试。 数据库很简单, 目的是让DataForm直接绑定一个RiaServices生成的Entity(已实现IEditableObject),另一个经过ViewModel包装(未另外实现IEditableObject)后再绑定。 运行结果:

public class SimpleStudentVM
{
    public Student FirstStudent
    {
        get { return new Student { Id = 1, Name = "张华", Age = 25 }; }
    }
}
public class WrappedStudentVM
{
    public StudentViewModel FirstStudent
    {
        get { return new StudentViewModel(new Student { Id = 1, Name = "张华", Age = 25 }); }
    }
}
public class StudentViewModel : NotificationObject
{
    private Student _student;
    public StudentViewModel(Student s)
    {
        _student = s;
    }
    public int Id
    {
        get { return _student.Id; }
        set
        {
            _student.Id = value;
            RaisePropertyChanged("Id");
        }

    }
    public string Name
    {
        get { return _student.Name; }
        set
        {
            _student.Name = value;
            RaisePropertyChanged("Name");
        }
    }
    public int Age
    {
        get { return _student.Age; }
        set
        {
            _student.Age = value;
            RaisePropertyChanged("Age");
        }
    }
}
<Grid x:Name="LayoutRoot">
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <StackPanel>
        <StackPanel.DataContext>
            <vm:SimpleStudentVM />
        </StackPanel.DataContext>
        <tookit:DataForm CurrentItem="{Binding FirstStudent}" />
    </StackPanel>

    <StackPanel Grid.Column="1">
        <StackPanel.DataContext>
            <vm:WrappedStudentVM />
        </StackPanel.DataContext>
        <tookit:DataForm CurrentItem="{Binding FirstStudent}" />
    </StackPanel>
</Grid>
可以看出来自动生成的Entity被ViewModel包装后再绑定DataForm时,因为它没有实现IEditableObject,所以不能参与进Cancel(实际就是Undo),界面上也不会显示Cancel按钮。且绑定自定义ViewModel后,必录项对应Label加粗也没了。 当然可以在自定义ViewModel时,实现IEditableObject,使实体对应ViewModel获得可以Undo的好处,还有用[Required]等重新标识必录项。问题是, 这是不是很繁琐呢? 所以就回到我最开始的问题了,既然RiaServices自动生成的Entity这么强大,还有很多方面都面面俱到(它还默认实现了INotifyDataErrorInfo,你想,如果在自己的ViewModel里再去实现这个接口……),因为DataForm直接绑定的是经过‘缩减’的对象,在这种情况下反不如直接绑定RiaServices自动生成的entity. [/quote] 所以才会把Model作为ViewModel的一个属性来,然后你要修改的部分就直接绑在Model上,毕竟这个Model本身不能扩展的。
vbfool 2014-05-09
  • 打赏
  • 举报
回复
引用 6 楼 adodotnet 的回复:
[quote=引用 5 楼 vbfool 的回复:] 你可以把Model作为ViewModel的一个属性来用,我就是这么干的,而且SimpleMvvM框架也是这么干的。
thank you. 我回的“比如Model里的bool,可以在传递给View之前,在ViewModel里转换成View关心的Visibility.Visible/Collapse等"这个不恰当。ViewModel里不应该有Visibility.Visible这样的东东。这个先这么地。 该回到标题的问题了。就是DataForm参与MVVM时,在什么时机把录入的数据提交到服务器的问题。一般是EventToCommand的方式,也就是DataForm的EditEnded事件绑定到ViewModel的Command这样子做吗?按我的要求那就是DataForm绑定了一个new出来的Entity,或像你所说的包装后的对应单个Entity的ViewModel,点击DataForm的"OK"时,也就是尝试Commit数据了,如果验证有问题就不会触发EditEnded事件,验证都通过时才会触发EditEnded是吧?然后因为ViewModel绑定了EditEnded事件,所以保证了至少客户端数据合法性,再在ViewModel里把新录入的合法数据提交到服务器。 现在没条件试,看到你的回复就直接回了:-) [/quote] 我记得这种时候还是靠IEditaleObject的吧
adodotnet 2014-05-09
  • 打赏
  • 举报
回复
引用 5 楼 vbfool 的回复:
你可以把Model作为ViewModel的一个属性来用,我就是这么干的,而且SimpleMvvM框架也是这么干的。
thank you. 我回的“比如Model里的bool,可以在传递给View之前,在ViewModel里转换成View关心的Visibility.Visible/Collapse等"这个不恰当。ViewModel里不应该有Visibility.Visible这样的东东。这个先这么地。 该回到标题的问题了。就是DataForm参与MVVM时,在什么时机把录入的数据提交到服务器的问题。一般是EventToCommand的方式,也就是DataForm的EditEnded事件绑定到ViewModel的Command这样子做吗?按我的要求那就是DataForm绑定了一个new出来的Entity,或像你所说的包装后的对应单个Entity的ViewModel,点击DataForm的"OK"时,也就是尝试Commit数据了,如果验证有问题就不会触发EditEnded事件,验证都通过时才会触发EditEnded是吧?然后因为ViewModel绑定了EditEnded事件,所以保证了至少客户端数据合法性,再在ViewModel里把新录入的合法数据提交到服务器。 现在没条件试,看到你的回复就直接回了:-)
adodotnet 2014-05-09
  • 打赏
  • 举报
回复
引用 7 楼 vbfool 的回复:
我记得这种时候还是靠IEditaleObject的吧

如果View绑定的是经过包装的ViewModel,比如如下情况:
View-StudentViewModel-Student(已经默认实现IEditableObject),那么它们各自在DataForm上的表现如何,我做了一下测试。

数据库很简单,


目的是让DataForm直接绑定一个RiaServices生成的Entity(已实现IEditableObject),另一个经过ViewModel包装(未另外实现IEditableObject)后再绑定。

运行结果:




public class SimpleStudentVM
{
public Student FirstStudent
{
get { return new Student { Id = 1, Name = "张华", Age = 25 }; }
}
}


public class WrappedStudentVM
{
public StudentViewModel FirstStudent
{
get { return new StudentViewModel(new Student { Id = 1, Name = "张华", Age = 25 }); }
}
}
public class StudentViewModel : NotificationObject
{
private Student _student;
public StudentViewModel(Student s)
{
_student = s;
}
public int Id
{
get { return _student.Id; }
set
{
_student.Id = value;
RaisePropertyChanged("Id");
}

}
public string Name
{
get { return _student.Name; }
set
{
_student.Name = value;
RaisePropertyChanged("Name");
}
}
public int Age
{
get { return _student.Age; }
set
{
_student.Age = value;
RaisePropertyChanged("Age");
}
}
}


<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<StackPanel.DataContext>
<vm:SimpleStudentVM />
</StackPanel.DataContext>
<tookit:DataForm CurrentItem="{Binding FirstStudent}" />
</StackPanel>

<StackPanel Grid.Column="1">
<StackPanel.DataContext>
<vm:WrappedStudentVM />
</StackPanel.DataContext>
<tookit:DataForm CurrentItem="{Binding FirstStudent}" />
</StackPanel>
</Grid>


可以看出来自动生成的Entity被ViewModel包装后再绑定DataForm时,因为它没有实现IEditableObject,所以不能参与进Cancel(实际就是Undo),界面上也不会显示Cancel按钮。且绑定自定义ViewModel后,必录项对应Label加粗也没了。
当然可以在自定义ViewModel时,实现IEditableObject,使实体对应ViewModel获得可以Undo的好处,还有用[Required]等重新标识必录项。问题是, 这是不是很繁琐呢?

所以就回到我最开始的问题了,既然RiaServices自动生成的Entity这么强大,还有很多方面都面面俱到(它还默认实现了INotifyDataErrorInfo,你想,如果在自己的ViewModel里再去实现这个接口……),因为DataForm直接绑定的是经过‘缩减’的对象,在这种情况下反不如直接绑定RiaServices自动生成的entity.

vbfool 2014-05-09
  • 打赏
  • 举报
回复
引用 4 楼 adodotnet 的回复:
[quote=引用 3 楼 vbfool 的回复:] 虽然是增加了很多代码,但是同样也提升了灵活性。 比如我在一个列表里,每条记录都有一个按钮,可能执行的功能和你的Model没多大关系,这时候,每个Model外包一个ViewModel,就是个不错的选择。
明白你的意思,比如Model里的bool,可以在传递给View之前,在ViewModel里转换成View关心的Visibility.Visible/Collapse等。ViewModel增加的Button也可以用来对‘单个记录’做些事情。实际上典型的MVVM示例也是这么做的。 只是到了RiaServices时,客户端生成的Entity简直就像是MVVM-Ready一样健壮,什么INotifyPropertyChanged, IEditibleObject等都实现了, 字段的Validation及ValidationEorr的通知它都包办了。说的夸张点,就像是ViewModel要我实现的话,似乎都不可能实现的比它更完善的感觉。 我目前的想法是这个事情就看情况而定。需要一些灵活性和更多控制的时候就在'单个Model'级别上用ViewModel包装一下,如果不是那么复杂情况,那就不包装类级别的model,而是通直接把model object(或它的集合)通过ViewModel来expose给View. [/quote] 你可以把Model作为ViewModel的一个属性来用,我就是这么干的,而且SimpleMvvM框架也是这么干的。
adodotnet 2014-05-08
  • 打赏
  • 举报
回复
引用 3 楼 vbfool 的回复:
虽然是增加了很多代码,但是同样也提升了灵活性。 比如我在一个列表里,每条记录都有一个按钮,可能执行的功能和你的Model没多大关系,这时候,每个Model外包一个ViewModel,就是个不错的选择。
明白你的意思,比如Model里的bool,可以在传递给View之前,在ViewModel里转换成View关心的Visibility.Visible/Collapse等。ViewModel增加的Button也可以用来对‘单个记录’做些事情。实际上典型的MVVM示例也是这么做的。 只是到了RiaServices时,客户端生成的Entity简直就像是MVVM-Ready一样健壮,什么INotifyPropertyChanged, IEditibleObject等都实现了, 字段的Validation及ValidationEorr的通知它都包办了。说的夸张点,就像是ViewModel要我实现的话,似乎都不可能实现的比它更完善的感觉。 我目前的想法是这个事情就看情况而定。需要一些灵活性和更多控制的时候就在'单个Model'级别上用ViewModel包装一下,如果不是那么复杂情况,那就不包装类级别的model,而是通直接把model object(或它的集合)通过ViewModel来expose给View.
vbfool 2014-05-08
  • 打赏
  • 举报
回复
引用 2 楼 adodotnet 的回复:
[quote=引用 1 楼 vbfool 的回复:] ICommand有个CanExecute的方法。 当然,我觉得可能和你理解的不一样,WCFRIA生成那个,应该说还是Model,ViewModel确实是要在外边包一层的,因为ViewModel本来就应该只提供View上才有的输入输出。
对。RiaServices生成的那个,应该看作Model. 当我问这个问题时,具体讲是在问RiaServices客户端生成的Model需不需要再包装一下。 比如数据库中有Product表,RiaServices客户端生成一个叫Proudct的Model是吧?这个东西是不是需要包装这个问题。比如手动再建下个ProductViewModel。那么如果是Product的列表时呢,向View提供的是ProductViewModel的集合。 这个问题一直让我有些困惑来着。 我查了一下,有人建议,从纯粹的设计角度,理想状态是View不应该知道任何Model的事,View只和ViewModel打交道。但实际上这样子会增加很多代码量,增加复杂度和难以维护。所以一般最佳作法是:It's generally best to simply expose your model objects(or collections of objects)directly to the view via a property on the ViewModel. [/quote] 虽然是增加了很多代码,但是同样也提升了灵活性。 比如我在一个列表里,每条记录都有一个按钮,可能执行的功能和你的Model没多大关系,这时候,每个Model外包一个ViewModel,就是个不错的选择。
adodotnet 2014-05-08
  • 打赏
  • 举报
回复
引用 1 楼 vbfool 的回复:
ICommand有个CanExecute的方法。 当然,我觉得可能和你理解的不一样,WCFRIA生成那个,应该说还是Model,ViewModel确实是要在外边包一层的,因为ViewModel本来就应该只提供View上才有的输入输出。
对。RiaServices生成的那个,应该看作Model. 当我问这个问题时,具体讲是在问RiaServices客户端生成的Model需不需要再包装一下。 比如数据库中有Product表,RiaServices客户端生成一个叫Proudct的Model是吧?这个东西是不是需要包装这个问题。比如手动再建下个ProductViewModel。那么如果是Product的列表时呢,向View提供的是ProductViewModel的集合。 这个问题一直让我有些困惑来着。 我查了一下,有人建议,从纯粹的设计角度,理想状态是View不应该知道任何Model的事,View只和ViewModel打交道。但实际上这样子会增加很多代码量,增加复杂度和难以维护。所以一般最佳作法是:It's generally best to simply expose your model objects(or collections of objects)directly to the view via a property on the ViewModel.
vbfool 2014-05-08
  • 打赏
  • 举报
回复
ICommand有个CanExecute的方法。 当然,我觉得可能和你理解的不一样,WCFRIA生成那个,应该说还是Model,ViewModel确实是要在外边包一层的,因为ViewModel本来就应该只提供View上才有的输入输出。

8,756

社区成员

发帖
与我相关
我的任务
社区描述
WPF/Silverlight相关讨论
社区管理员
  • WPF/Silverlight社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧