为了让本例支持更复杂的应用场景,我们这次要针对一个列表进行操作。
1:建立领域模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public
class
StudentTeam: NotificationObject
{
string
teamName;
public
string
TeamName
{
get
{
return
teamName;
}
set
{
teamName = value;
this
.RaisePropertyChanged(() =>
this
.TeamName);
}
}
//获取模拟数据
internal
List<Student> MockGetStudents()
{
List<Student> students =
new
List<Student>();
students.Add(
new
Student() { FirstName =
"f1"
, LastName =
"l1"
});
students.Add(
new
Student() { FirstName =
"f2"
, LastName =
"l2"
});
students.Add(
new
Student() { FirstName =
"f3"
, LastName =
"l3"
});
students.Add(
new
Student() { FirstName =
"f4"
, LastName =
"l4"
});
return
students;
}
}
|
领域模型完成的工作很简单,就是为了获取到一个详细的列表。同样,我们不关心数据来自与何方,这里只是进行了模拟。
2:建立ViewModel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public
class
StudentListViewModel : NotificationObject
{
public
StudentListViewModel()
{
students = (
new
StudentTeam()).MockGetStudents();
}
List<Student> students;
public
List<Student> Students
{
get
{
return
students;
}
set
{
students = value;
this
.RaisePropertyChanged(() =>
this
.Students);
}
}
string
someState =
string
.Empty;
public
string
SomeState
{
get
{
return
someState; }
set
{ someState = value;
this
.RaisePropertyChanged(() =>
this
.SomeState); }
}
public
void
Select()
{
SomeState =
"a"
;
}
public
void
UnSelect()
{
SomeState =
"b"
;
}
}
|
vm中方法Select用于绑定某条记录被选择时触发,UnSelect则用于取消选择时触发。
Students用于列表绑定,SomeState用于显示选定状态。
3:建立View
注意图中红线部分。
有一个细节要注意到,每一条记录都有一个CheckBox,由于采用了数据模版,默认CheckBox的绑定源对应的是ListBox的绑定源,而实际上,我们需要CheckBox对应的绑定源是VM对象,因为VM对象中才有Select和UnSelect方法,所以CheckBox的绑定源我们对应到了Grid上面。
现在,可以运行了。界面如下:
4:问题来了,如何让绑定方法带参数
由于Select和UnSelect都无法带参数,所以在VM中我们根本不知道是哪条记录被选中了。这个问题在这里特别突出,默认的命令绑定中Button的Command就是可以带参数的,但是这里的CallMethodAction就不允许带参数。
有人觉得在VM中新增一个属性用来绑定当前记录就可以的,但是个人认为这是一种非常丑陋的实现。
查看Prism和MVVM Light的源码,都创建了一个类似的类型,在Prism中是DelegateCommand,在MVVM Light中是RelayCommand,它们最终都继承自ICommand接口。使用这类类型,可以支持绑定带参数方法。
不过,即便我们不使用这两个框架,有几类方法仍然能支持使用参数。如:
1:在前台使用System.Windows.Interactivity命名空间下的InvokeCommandAction,在后台则使用Microsoft.Expression.Interactivity.Core下的ActionCommand也可以解决这个问题。当前SL子集中(4.0及以下)我没有直接实现ICommand接口的类型。
2:或者,干脆我们自己实现一个Command类型。
首先,我们来看第一种方法。
5:使用InvokeCommandAction和ActionCommand解决方法带参数
前台代码需修改的部分:
VM部分代码为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
public
class
StudentListViewModel : NotificationObject
{
public
StudentListViewModel()
{
students = (
new
StudentTeam()).MockGetStudents();
Selected =
new
ActionCommand(
this
.Select);
UnSelected =
new
ActionCommand(
this
.UnSelect);
}
List<Student> students;
public
List<Student> Students
{
get
{
return
students;
}
set
{
students = value;
this
.RaisePropertyChanged(() =>
this
.Students);
}
}
string
someState =
string
.Empty;
public
string
SomeState
{
get
{
return
someState; }
set
{ someState = value;
this
.RaisePropertyChanged(() =>
this
.SomeState); }
}
public
ICommand Selected {
get
;
private
set
; }
void
Select(
object
obj)
{
SomeState = (obj
as
Student).FirstName;
}
public
ICommand UnSelected {
get
;
private
set
; }
void
UnSelect(
object
obj)
{
SomeState = (obj
as
Student).LastName;
}
}
|
运行代码,你会发现一切已经如我们所愿。这里不妨停下来提供一下源代码:http://files.cnblogs.com/luminji/SilverlightApplication3.rar
6:实现自己的ICommand类型
所谓实现自己的ICommand,其实就是参考两大框架的代码,取而用之,在这里使用的MVVM Light的RelayCommand,并稍稍改之(泛型,其实你会注意到这里的泛型实现没有任何意义,但是我懒得再去修改了)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
public
class
RelayCommand<T> : ICommand
where
T :
class
{
private
readonly
Action<T> _execute;
private
readonly
Func<
bool
> _canExecute;
public
RelayCommand(Action<T> execute)
:
this
(execute,
null
)
{
}
public
RelayCommand(Action<T> execute, Func<
bool
> canExecute)
{
if
(execute ==
null
)
{
throw
new
ArgumentNullException(
"execute"
);
}
_execute = execute;
_canExecute = canExecute;
}
public
event
EventHandler CanExecuteChanged;
public
void
RaiseCanExecuteChanged()
{
var
handler = CanExecuteChanged;
if
(handler !=
null
)
{
handler(
this
, EventArgs.Empty);
}
}
public
bool
CanExecute(
object
parameter)
{
return
_canExecute ==
null
?
true
: _canExecute();
}
public
void
Execute(
object
parameter)
{
_execute(parameter
as
T);
}
}
|
相应的,因为使用自己的ICommand类型,所以,VM的代码也稍稍进行了修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public
class
StudentListViewModel : NotificationObject
{
public
StudentListViewModel()
{
students = (
new
StudentTeam()).MockGetStudents();
Selected =
new
RelayCommand<Student>(
this
.Select);
UnSelected =
new
RelayCommand<Student>(
this
.UnSelect);
}
List<Student> students;
public
List<Student> Students
{
get
{
return
students;
}
set
{
students = value;
this
.RaisePropertyChanged(() =>
this
.Students);
}
}
string
someState =
string
.Empty;
public
string
SomeState
{
get
{
return
someState; }
set
{ someState = value;
this
.RaisePropertyChanged(() =>
this
.SomeState); }
}
public
ICommand Selected {
get
;
private
set
; }
void
Select(Student obj)
{
SomeState = obj.FirstName;
}
public
ICommand UnSelected {
get
;
private
set
; }
void
UnSelect(Student obj)
{
SomeState = obj.LastName;
}
|
这部分的代码可以直接重构上部分提供的源码,故不再提供下载了。
7:题外话
既然已经完全实现了自己创建类型的MVVM简单框架,想要把这部分功能使用Prism或Light来实现,就是轻而易举的事情了,只要将相应的类型用两个框架中对应的类型就可以了。