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

在 Windows Phone 7 上处理日期

2010 年 12 月 2 日

CPOL

6分钟阅读

viewsIcon

29065

本文介绍了各种数据编辑器,所有这些编辑器都是借助 Resco 的 MobileForms Toolkit 构建的。

本文介绍了各种数据编辑器,所有这些编辑器都是借助 Resco 的 MobileForms Toolkit 构建的。

引言

Windows Phone 7 (WP7) 编程结合了多种技术

  • .NET (WP7 使用了完整 .NET 类库的一个子集。)
  • C#
  • Silverlight for WP7。(桌面 SVL 的简化版。)

对于一位从 (例如) Windows Mobile 编程转过来的程序员来说,精通这些技术是一个真正的挑战。本文将展示如何使用 Resco 的 MobileForms Toolkit 来处理日期。

必备组件

目标读者是具备 Silverlight 阅读能力的 C# 程序员。至少,读者应该理解基本的 Silverlight 控件和布局。

您需要安装 Windows Phone Developer Tools。

此外,您应该从 http://www.resco.net/developer/mobileformstoolkit/ 安装 Resco MobileForms Toolkit, Windows Phone 7 Edition。该工具包包含一套有用的控件,可以简化 Windows Phone 7 编程。

简单用例

MobileForms Toolkit

上面的截图是所有 Windows Phone 7 用户都非常熟悉的。是的,就是那个著名的日期/时间选择器。

MobileForms Toolkit (目前) 不直接提供对这些选择器的访问,而是将它们打包成几个更高级别的控件。以下截图展示了其中三个控件的实际应用。

MobileForms Toolkit

这是相应的 XAML 代码

 xmlns:r="clr-namespace:Resco.Controls;assembly=Resco.Controls"
		…
	<r:DateEditor Text="{Binding Path=ModifiedOn}"  
             TextDecoration="Underline" FormatString="{}{0:D}" />1
	<r:TimeEditor Text="{Binding Path=ModifiedOn}"  
             FormatString="{}Modified on {0:t}" />
	<r:DetailItemDateTime Label="Last Transaction"  DataMember="ModifiedOn" />
	<r:DetailItemDateTime Label="Last Transaction"  
             DataMember="ModifiedOn" TimeVisibility="Collapsed" />
1) 前导的 {} 不是实际格式字符串的一部分。它是一个 XAML 转义符,允许后续使用花括号。

DataContext 被设置为一个具有属性的对象

public DateTime ModifiedOn { get; set; }

所有控件都充当该属性的编辑器。(效果是通过绑定实现的。)

DateEditor/TimeEditor 是文本控件,点击后会打开相应的选择器。上面的代码演示了自定义格式;通常情况下您不会使用它。

DetailItemDateTime 使用所有 DetailItem 派生类 2 的典型外观,即 (可选) 标签和特定的外观与感觉。您可以将其设置为日期或时间编辑器 (使用 DateVisibility/TimeVisibility 属性),或者设置为完整的 DateTime 编辑器 (默认)。除了绑定,您还可以使用更简单的 DataMember 语法 3。您可以自定义日期和时间格式以及许多其他属性。

2) 这些控件主要用于 DetailView 窗体,在那里它们提供通用的外观与感觉。如示例代码所示,它们也可以独立使用。

通过显式绑定

3) DataMember 指的是要在类代码中创建的用于绑定的属性名称。在 .NET 世界中,这种术语通常用于与数据相关的控件。实际上,我们可以替换
		DataMember="ModifiedOn"
			by explicit binding
			Value={Binding Path=ModifiedOn, Mode=TwoWay}

关于基本的日期控件就讲到这里。未来会根据用户请求添加更多类似的控件。本文的其余部分将演示另一个控件 – MonthCalendar 的一些有趣用法。

使用 MonthCalendar 选择日期

MonthCalendar 是一个相当高级的控件。它的任务是示意性地呈现一系列约会。然而,我们现在只需要能够选择单个日期。没有约会 4,只需要对选择的响应。

4) 换句话说,我们将使用 null DataSource。

UI 如下所示

MobileForms Toolkit

MonthCalendar 控件位于底部。您可以列出单个月份并选择合适的日期。

顶部的 TextBlock 将反映当前的选择。这只是为了证明概念。您的布局可能会有所不同 – 例如,带有确定/取消按钮的弹出对话框。

重要的代码几乎微不足道

 // Excerpt from MonthCalendarPage.xaml
	<StackPanel Orientation="Horizontal">
	<r:PhoneTextControl  Text="Selected date:  />
	<r:PhoneTextControl  x:Name="m_selected" />
	</StackPanel>
	<r:MonthCalendar x:Name="m_calendar"  Height="480" SelectionChanged="DayChanged"/>

	    // MonthCalendarPage.xaml.cs
		// Calendar has null DataSource; we only react to the item  selection.
		public partial class MonthCalendarPage : PhoneApplicationPage
		{
		public  MonthCalendarPage()
		{
		InitializeComponent();
		}
		                private  void DayChanged(object sender, EventArgs e)
		{
		DateTime?  dt = m_calendar.SelectedDate;
		if  (dt != null)
		m_selected.Text  = m_calendar.SelectedDate.Value.ToString("d");
		//  NavigationService.GoBack();         
                  // Also  possible: Return to previous page
		}
		}

XAML 代码定义了 MonthCalendar 实例和一个提供日期选择反馈的文本字段。正如我们上面提到的——这只是一个概念证明。对于实际使用,您需要以某种方式将 MonthCalendarPage 打包到一个新的 DateEditor 类中。

  • 它可以是一个 PhoneTextControl 派生类 (即一个具有格式化功能的 TextBlock),可以绑定到基于 DateTime 的属性。该控件将在 Popup 中打开 MonthCalendarPage,作为对 MouseLeftButtonDown 事件的响应。
  • 或者,它可以是一个 HyperlinkButton 派生控件,能够绑定到基于 DateTime 的属性。此解决方案将使用 HyperlinkNavigation 替换 Popup。

这两种解决方案都需要额外的非平凡的编程。直接使用 MonthCalendarPage 稍微容易一些。(NavigationService.GoBack()) 然而,您仍然需要处理编辑日期值的传输。

使用 MonthCalendar 的日期多选器

这是另一个有趣的例子——使用 MonthCalendar 控件来选择多个日期。该控件允许您逐月翻页并选择/取消选择单个日期。页面顶部显示了当前选择的简要摘要。

MobileForms Toolkit

日历实现为 1 天事件的集合;事件由 DateTime 对象表示 5。除了 ICalendarDataSource 实现,我们将提供两个方法

  • AddOrRemove() 用作切换——将新日期添加到列表中,删除旧日期。
  • ToString() 提供当前选择的简要摘要。
5 Calendar 是一个实现 ICalendarDataSource 的类——一个极简的用于约会建模的接口。约会 (事件) 是一个时间间隔加上一些 GUI 表示。MonthCalendar 本身提供了约会的默认可视化 (蓝色矩形)。这已经足够好了。关于时间,我们只需要一个特征——日期。因此,我们不需要任何特殊的类,而是可以直接使用标准的 DateTime 类。
public class MyCalendar : ICalendarDataSource
	{
	//  An event is represented by DateTime object
	List<DateTime>  m_list = new List<DateTime>();
	//  Return list of events between start-end dates
	IEnumerable  ICalendarDataSource.GetRange(DateTime start, DateTime end)
	{
	var  list = new List<Object>();
	foreach(  DateTime d in m_list)   {
	if(  start <= d  &&  d <= end )
	list.Add(d);
	}
	return  list;
	}
	//  Returns time interval describing given event
	void  ICalendarDataSource.GetElementRange(object elem, 
             out DateTime start, out DateTime  end)      {
    // In  our case the event itself describes the time interval
	start  = end = (DateTime)elem ;
	}
	//  Reaction to the click
	public  void AddOrRemove(DateTime dt)
	{
	foreach  (DateTime d in m_list)  {
	if  (d == dt)  {
	m_list.Remove(d);                // Old event is removed
	return;
	}
	}
	m_list.Add(dt);                     // No old event found =>  add a new event
	}
	// Used  for presentation purposes
		public  override string ToString()
		{
		DateTime  min = DateTime.MaxValue;
		DateTime  max = DateTime.MinValue;
		foreach  (DateTime d in m_list)  {
		if  (min > d)  min = d;
		if  (max < d  max = d;
		}
		if  (m_list.Count == 0)
		return  "Nothing; click some day(s)";
		if  (m_list.Count == 1)
		return  min.ToString("d");
		else
		return  String.Format("{0} days; {1:d} - {2:d}", m_list.Count, min, max);
		}
	}

我们终于可以实现 MonthCalendarPage 了。

        // C# code
		public partial class MonthCalendarPage : PhoneApplicationPage
		{
		// We  use static DataSource to provide continuity between
                  // multiple uses of  MonthCalendarPage.
		//Replace  by the implementation that best suits to your needs.
		static  MyCalendar DataSource = new MyCalendar();
		public  ICalendarDataSource MyCalendar  {
		get  { return DataSource; }
		}
		                public  MonthCalendarPage()  {
			DataContext  = this;
			InitializeComponent();
			m_selected.Text  = DataSource.ToString();
			}
			                //  SelectionChanged event handler
			private  void DayChanged(object sender, EventArgs e)   {
			DateTime?  val = m_calendar.SelectedDate;
			if  (val != null)  {
			DateTime  dt = val.Value;
			DataSource.AddOrRemove(dt);
			m_selected.Text  = DataSource.ToString();
			//  A trick to force calendar refresh because
                            // theResetList method is private.
			//m_calendar.ResetList();
			// (Will be available in  next release.)
			DateTime  x = m_calendar.Date;
			//  Just force Date change
			m_calendar.Date  = m_calendar.Date.AddDays(1);
			// Revert  the original ate value
			m_calendar.Date  = x;
			}
			}
			}

	// Excerpt from MonthCalendarPage.xaml
	<StackPanel Grid.Row="1">
	<StackPanel  Orientation="Horizontal">
	<r:PhoneTextControl  Text="Selected date:" Margin="10" />
	<r:PhoneTextControl  x:Name="m_selected" />
	</StackPanel>
	<r:MonthCalendar  x:Name="m_calendar" Height="480"  
	    SelectionChanged="DayChanged"/>
	</StackPanel>

StringToDayTimeConverter

您是否曾经想过日期如何输入到 Xaml 中?例如

 <WeekCalendar  Minimum="11/1/2010"  Maximum="11/30/2011" Date="today" />

嗯,Silverlight 比 WPF 简单,但这却是许多你真正怀念 WPF 功能的例子之一。我们无法为 (例如) 系统类添加读取日期的能力,但如果代码在我们控制之下,那么我们所需要做的就是为我们想在 Xaml 中使用的属性定义合适的 TypeConverter 属性。

这是一个 WeekCalendar 控件如何定义 Xaml 代码中使用的属性的示例。

        // The only added line
		[TypeConverter(typeof(StringToDateTimeConverter))]
		public DateTime Minimum   { get; set; }

StringToDateTimeConverter 类是 Resco.Controls.Tools 命名空间的一部分。这个转换器的优点是它不仅处理大量的日期格式 (它基于 DateTime.TryParse()),还处理特殊字符串“today”和“Today”。

请注意,您无需为数值类型添加任何转换器,因为 Silverlight 会自动处理转换。

下一步

Resco 是一家在移动编程领域拥有悠久传统的公司,涵盖了多个平台以及终端用户应用程序和开发人员工具。该工具包——正如现在发布的——自第一个版本以来已经成熟了一些。然而,这仍然是一个早期开发阶段。在不久的将来,现有控件将得到增强,并会添加新控件。

任何反馈都欢迎——无论是在这里还是直接在 Resco 论坛上。

关于作者

Jan Slodicka。编程超过 30 年。涵盖了多个桌面平台和编程语言。自 2003 年以来一直为 Resco 从事移动技术工作——Palm OS、Windows Mobile,现在是 Windows Phone 7。

您可以通过以下方式联系我:jano (at) resco (dot) net

© . All rights reserved.