Silverlight Super Tabs Interface (使用 ViewModel / MVVM)






4.94/5 (17投票s)
在同一个 Tab Control 中动态创建不同类型的选项卡。

一个你可能会爱死或恨死的用户界面
在线示例: http://silverlight.adefwebserver.com/SimpleAttendanceTabWeb
我喜欢选项卡。使用选项卡就像把纸张放在桌子上方便取用,而不是每次需要时都要去文件柜里翻找。
我正在为我流行的ADefHelpDesk.com开源项目开发一个Silverlight客户端,在看到用户打开多个浏览器窗口来方便地回到他们正在处理的帮助台票据后,我意识到Silverlight版本需要使用选项卡。
我遇到的问题是,无法轻松地将主搜索屏幕作为一个选项卡,而将其他帮助台票据的选项卡放在同一个选项卡控件中。一个Silverlight选项卡控件希望绑定到一个单一类型的集合。
答案,就像大多数情况下一样,是简单地使用行为。
应用程序

应用程序的先前版本,在文章Silverlight Attendance Demo using Sterling Silverlight Database中有所介绍,只允许一次选择一天。

在此版本中,我们添加了以下功能:
- 第一个选项卡是每个学生的出勤总计的列表,这个选项卡无法关闭。
- 当您选择概览选项卡时,其计算将始终自动更新。
- 可以为每个出勤录入日期打开无限个选项卡。
- 如果您使用日历选择一个已打开的选项卡,它将简单地切换到该选项卡。
- 排序适用于除包含单选按钮的列之外的所有列。
要选择一个日期来录入出勤,您可以点击出勤框旁边的图标,弹出日历并选择日期。选择日期后,点击打开日期按钮,在选项卡中打开该日期。
选项卡头控件

首先,我们创建代表概览选项卡和出勤选项卡的Silverlight控件作为选项卡头。
概览选项卡头(MainTabHeader.xaml)只是一个包含“概览”文字的TextBlock控件。我们费力创建这样一个控件的唯一原因是为了方便设计者对其进行样式设置。

出勤选项卡头(AttendanceTabHeader.xaml)稍微复杂一些,因为它有一个ViewModel。ViewModel允许动态设置文本,并包含一个ICommand
,允许关闭选项卡。
它还包含一个选项卡控件的实例,以及“选项卡所在的选项卡头”,以便可以关闭选项卡。
这是关闭选项卡的ICommand
public ICommand CloseTabCommand { get; set; }
public void CloseTab(object param)
{
// Remove this TabItem from the Tab control
objTabControl.Items.Remove(objTabItem);
}
private bool CanCloseTab(object param)
{
return true;
}
选项卡内容控件

Overview.xaml控件包含每个Student
的出勤总计。
它包含一个由LayoutRoot加载触发的ICommand
。这使得每当它所在的选项卡获得焦点时,它都会被刷新。
public ICommand ComputeTotalsCommand { get; set; }
public void ComputeTotals(object param)
{
if (!(DesignerProperties.IsInDesignTool))
{
LoadData();
}
}
private bool CanComputeTotals(object param)
{
return true;
}
以下方法计算总计
public void LoadData()
{
bool hasKeys = false;
foreach (var item in SterlingService.Current.Database.Query<Student, int>())
{
hasKeys = true;
break;
}
if (hasKeys)
{
// Clear All Collections
Students.Clear();
Enrollments.Clear();
colAttendance.Clear();
colStudentOverview.Clear();
// Get the data
foreach (var item in SterlingService.Current.Database.Query<Student, int>())
{
Students.Add(item.LazyValue.Value);
}
foreach (var item in SterlingService.Current.Database.Query<Enrollment, int>())
{
Enrollments.Add(item.LazyValue.Value);
}
foreach (var item in SterlingService.Current.Database.Query<Attendance, string>())
{
colAttendance.Add(item.LazyValue.Value);
}
// Create the Query
var result = from Student in Students
select new StudentOverview
{
StudentId = Student.StudentId,
Name = Student.Name,
P = (from objEnrollment in Enrollments
from objAttendance in colAttendance
where objEnrollment.StudentId == Student.StudentId
where objEnrollment.EnrollmentId ==
objAttendance.EnrollmentId
where objAttendance.AttendanceStatus == "P"
select objAttendance).Count(),
T = (from objEnrollment in Enrollments
from objAttendance in colAttendance
where objEnrollment.StudentId == Student.StudentId
where objEnrollment.EnrollmentId ==
objAttendance.EnrollmentId
where objAttendance.AttendanceStatus == "T"
select objAttendance).Count(),
E = (from objEnrollment in Enrollments
from objAttendance in colAttendance
where objEnrollment.StudentId == Student.StudentId
where objEnrollment.EnrollmentId ==
objAttendance.EnrollmentId
where objAttendance.AttendanceStatus == "E"
select objAttendance).Count(),
U = (from objEnrollment in Enrollments
from objAttendance in colAttendance
where objEnrollment.StudentId == Student.StudentId
where objEnrollment.EnrollmentId ==
objAttendance.EnrollmentId
where objAttendance.AttendanceStatus == "U"
select objAttendance).Count(),
};
// Fill the final Collection
foreach (var Student in result)
{
colStudentOverview.Add(Student);
}
}
}
AttendanceDay.xaml控件包含在单个日期注册的Students
的出勤信息。该控件的大部分代码都在文章Silverlight Attendance Demo using Sterling Silverlight Database中介绍。
行为

将所有这些联系在一起的是两个简单的行为。
AddOverviewToTabControl 行为

AddOverviewToTabControl
行为是两者中较简单的一个。它附加到选项卡控件,并且它所做的只是动态创建一个TabItem
并将Overview.xaml控件放置在选项卡控件上。

首先,我们在MainPage.xaml控件上放置一个选项卡控件。

接下来,我们将一个AddOverviewToTabControl
行为添加到TabControl
。

然后,我们将Behavior
绑定到作为Layoutroot
的Grid
。我们使用一个EventTrigger
,它将在Grid
加载时触发。
这是Behavior
的代码
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace SimpleAttendance
{
[System.ComponentModel.Description("Adds the OverView control to a Tab Control")]
public class AddOverviewToTabControl : TargetedTriggerAction<TabControl>
{
TabControl objTabControl;
protected override void OnAttached()
{
base.OnAttached();
objTabControl = (TabControl)(this.AssociatedObject);
}
protected override void OnDetaching()
{
base.OnDetaching();
}
protected override void Invoke(object parameter)
{
AddTabToTabControl();
}
private void AddTabToTabControl()
{
// Make a New TabItem
TabItem objTabItem = new TabItem();
// Make a instance of the MainTabHeader.xaml control
MainTabHeader objMainTabHeader = new MainTabHeader();
// Make a instance of the Overview.xaml control
Overview objOverview = new Overview();
// Set the Header to the MainTabHeader.xaml Control
objTabItem.Header = objMainTabHeader;
// Set the Content to the Overview.xaml Control
objTabItem.Content = objOverview;
// Add the TabItem to the TabControl
objTabControl.Items.Add(objTabItem);
}
}
}
AddAttendanceToTabControl 行为

AddAttendanceToTabControl
行为稍微复杂一些,因为它需要知道创建选项卡的出勤日期是什么。

当打开日期按钮触发ICommand
时,由AttendanceDate
属性的变化引发该行为。AttendanceDate
属性的值绑定到行为中的一个依赖属性。这是该行为的代码
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace SimpleAttendance
{
[System.ComponentModel.Description("Adds te Attendance control to a Tab Control")]
public class AddAttendanceToTabControl : TargetedTriggerAction<TabControl>
{
TabControl objTabControl;
#region AttendanceDayProperty
public static readonly DependencyProperty AttendanceDayProperty =
DependencyProperty.Register("AttendanceDay",
typeof(DateTime?), typeof(AddAttendanceToTabControl), null);
public DateTime? AttendanceDay
{
get
{
return (DateTime?)base.GetValue(AttendanceDayProperty);
}
set
{
base.SetValue(AttendanceDayProperty, value);
}
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
objTabControl = (TabControl)(this.AssociatedObject);
}
protected override void OnDetaching()
{
base.OnDetaching();
}
protected override void Invoke(object parameter)
{
if (AttendanceDay != null)
{
// See if the Tab is already added
var Tab = (from Tabs in objTabControl.Items.Cast<TabItem>()
where (Tabs.Tag as string) ==
AttendanceDay.Value.Ticks.ToString()
select Tabs).FirstOrDefault();
if (Tab == null)
{
AddTabToTabControl();
}
else // Tab already exists
{
// Set the Tab as selected
Tab.IsSelected = true;
}
}
}
private void AddTabToTabControl()
{
TabItem objTabItem = new TabItem();
// ** Tab Content **
AttendanceDay objAttendanceDay = new AttendanceDay();
// Get it's DataContext
AttendanceDayModel objAttendanceDayModel =
(AttendanceDayModel)objAttendanceDay.DataContext;
// Set the date
objAttendanceDayModel.SetDateCommand.Execute(AttendanceDay);
// ** Tab Header **
AttendanceTabHeader objAttendanceTabHeader = new AttendanceTabHeader();
// Get it's DataContext
AttendanceTabHeaderModel objAttendanceTabHeaderModel =
(AttendanceTabHeaderModel)objAttendanceTabHeader.DataContext;
// Set the Header Display
objAttendanceTabHeaderModel.HeaderDisplay =
String.Format("{0} {1}", AttendanceDay.Value.DayOfWeek.ToString(),
AttendanceDay.Value.ToShortDateString());
// Pass an instance of the TabControl to the View Model
// to allow this Tab to be removed from it
objAttendanceTabHeaderModel.objTabControl = objTabControl;
// Pass an instance of this TabItem to the View Model
// to allow this Tab to be removed from the TabControl
objAttendanceTabHeaderModel.objTabItem = objTabItem;
// Set the Tag on this Tab to the Ticks so we can easily find it
// in the Invoke method of this Behavior
objTabItem.Tag = AttendanceDay.Value.Ticks.ToString();
objTabItem.Header = objAttendanceTabHeader;
objTabItem.Content = objAttendanceDay;
objTabControl.Items.Add(objTabItem);
// Set the Tab as selected
objTabItem.IsSelected = true;
}
}
}
行为是关键
我最初为我面临的挑战创建了另一个解决方案。它包含一个自定义选项卡控件和大量模板的使用。然而,当我看到我已有的东西时,我知道我会避免对其进行任何更改或增强,因为它已经非常复杂了。在使用ViewModel / MVVM时,我们有时会发现自己制造了这些情况。
使用行为可以帮助我们避免过于复杂的架构。行为大多是小的原子操作,输入输出都很小。它们易于重用,并且易于被非程序员(如设计者)使用。大多数情况下,它们从“UI方面”进行操作。如果我们认为ViewModel在View的“后面”,那么我们可以认为行为在View的“前面”。从两个不同方面解决架构挑战可以打开很多可能的解决方案。
历史
- 2010年11月14日:初始帖子