65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight Super Tabs Interface (使用 ViewModel / MVVM)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (17投票s)

2010年11月14日

Ms-PL

4分钟阅读

viewsIcon

93811

downloadIcon

2966

在同一个 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)稍微复杂一些,因为它有一个ViewModelViewModel允许动态设置文本,并包含一个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;
}

选项卡内容控件

Click to enlarge image

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);
        }
    }
}

Click to enlarge image

AttendanceDay.xaml控件包含在单个日期注册的Students的出勤信息。该控件的大部分代码都在文章Silverlight Attendance Demo using Sterling Silverlight Database中介绍。

行为

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

AddOverviewToTabControl 行为

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

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

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

Click to enlarge image

然后,我们将Behavior绑定到作为LayoutrootGrid。我们使用一个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行为稍微复杂一些,因为它需要知道创建选项卡的出勤日期是什么。

Click to enlarge image

打开日期按钮触发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方面”进行操作。如果我们认为ViewModelView的“后面”,那么我们可以认为行为View的“前面”。从两个不同方面解决架构挑战可以打开很多可能的解决方案。

历史

  • 2010年11月14日:初始帖子
© . All rights reserved.