这里在Windows Phone中实现一个简单的记事本程序。
目标:
1、能够使用手指在页面上随意记录下信息。
2、能够实现 添加 、删除页,能够翻到上一页、下一页。
3、能够修改画布的背景色、画笔的粗细。
4、自动保存页面信息,并且能够保存记事本的相关状态信息。
大致效果如图所示:
下面是简单的实现过程
首先是绘制界面:
最主要的是InkPresenter这个控件,关于这个控件可以看一下我的另一篇文章:http://www.cnblogs.com/yinghuochong/archive/2011/09/13/2175285.html
这里添加了一个Rectangle 主要是为了给InkPresenter绘制一个边框效果。
然后是需要添加ApplicationBar,用来控制记事本的设置。Xaml大致代码如下:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="萤火虫作品 ---简易记事本" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="pageInfoTextBlock" Text="" Style="{StaticResource PhoneTextNormalStyle}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Stroke="Red"></Rectangle>
<InkPresenter Name="inkPresenter" Margin="2 2 2 2" ></InkPresenter>
</Grid>
</Grid>
<!--Sample code showing usage of ApplicationBar-->
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="/Images/appbar.add.rest.png" x:Name="addAppBarButton" Click="addAppBarButton_Click" Text="添加"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.back.rest.png" x:Name="backAppBarButton" Click="backAppBarButton_Click" Text="上一页"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.next.rest.png" x:Name="nextAppBarButton" Click="nextAppBarButton_Click" Text="下一页"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar.delete.rest.png" x:Name="deleteAppBarButton" Click="deleteAppBarButton_Click" Text="删除"/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem x:Name="swapColors" Text="改变背景颜色" Click="swapColors_Click"/>
<shell:ApplicationBarMenuItem x:Name="lightStroke" Click="setStrokeWidth_Click" Text="细线"/>
<shell:ApplicationBarMenuItem x:Name="mediumStroke" Click="setStrokeWidth_Click" Text="中线"/>
<shell:ApplicationBarMenuItem x:Name="heavyStroke" Click="setStrokeWidth_Click" Text="粗线"/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>
接着就是定义一个配置文件了,因为当页面从墓碑化中唤醒,或者重新启动的时候都要将记事本和上次关闭的时候尽量保持一致,这样能有更好的体验。
添加类NoteBookSettings 该类的属性主要有:
List<StrokeCollection> StrokeCollections 用来保存所有的线条 每页的线条用一个StrokeColeection表示;List中的每一项表示一页的线条;
int PageNum 表示记事本当前的页码
Color Forground 记事本的前景色
Color Background 记事本的背景色
int StrokeWidth 画笔线条的宽度。这里本来想定义一个DrawingAttributes类型的属性,这样可以设置不同的画笔样式,但是当背景颜色改变的时候就不好控制了,所以如果不改变背景颜色的话这里可以定义成DrawingAttributes类型 ,然后在主页面中通过设置其宽度和颜色可以实现 荧光笔,标记笔的效果。
然后定义一个Save()方法和一个Load()方法 将配置信息序列化后保存在隔离存储空间中或者将配置信息从文件中反序列化出来,这个就很简单了。
NoteBookSettings类的代码如下:
public class NoteBookSettings
{
//每个Stroke就是一条连续的线
//每当用户手指 touch屏幕->移动->抬起 整个过程就会产生一条Stroke ,在这个程序中用户会多次执行这个过程
//多个Stroke对象 会存储在StrokeCollection对象中,
//InkPresenter有一个Strokes属性,该属性是StrokeCollection类型, InkPresenter会将这些Srokes渲染到屏幕上
//因为这里有多个页面,所以每个页面需要一个StrokeCollection存储Strokes
public List<StrokeCollection> StrokeCollections { set; get; }
//页码
public int PageNum { set; get; }
public Color Forground { set; get; }
public Color Background { get; set; }
//线段的宽度
public int StrokeWidth { get; set; }
public NoteBookSettings()
{
PageNum = 0;
Forground=(Color)Application.Current.Resources["PhoneForegroundColor"];
Background = (Color)Application.Current.Resources["PhoneBackgroundColor"];
StrokeWidth = 1;
}
//将配置信息保存到隔离存储空间
public void Save()
{
IsolatedStorageFile isoStorageFile = IsolatedStorageFile.GetUserStoreForApplication();
//若文件已经存在,将会重新创建
IsolatedStorageFileStream isoStorageFileStream = isoStorageFile.CreateFile("NotebookSettings.xml");
StreamWriter writer = new StreamWriter(isoStorageFileStream);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(NoteBookSettings));
xmlSerializer.Serialize(writer, this);
writer.Close();
isoStorageFile.Dispose();
}
//加载配置信息
public static NoteBookSettings Load()
{
NoteBookSettings noteBookSettings;
IsolatedStorageFile isoStorageFile = IsolatedStorageFile.GetUserStoreForApplication();
if (isoStorageFile.FileExists("NotebookSettings.xml"))
{
IsolatedStorageFileStream isoStorageFileStream = isoStorageFile.OpenFile("NotebookSettings.xml", FileMode.Open);
StreamReader reader = new StreamReader(isoStorageFileStream);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(NoteBookSettings));
noteBookSettings = xmlSerializer.Deserialize(reader) as NoteBookSettings;
reader.Close();
}
else //如果是第一次加载
{
//这里将使用构造函数中的默认设置
noteBookSettings = new NoteBookSettings();
noteBookSettings.StrokeCollections = new List<StrokeCollection>();
noteBookSettings.StrokeCollections.Add(new StrokeCollection());
}
isoStorageFile.Dispose();
return noteBookSettings;
}
}
然后需要配置App.xaml.cs文件,当程序启动或激活时加载配置文件,退出或墓碑化时保存配置文件。
只需在App.xaml.cs中定义一个NoteBookSettings对象: public NoteBookSettings Settings{get;set;}
然后在Application_Launching 和Application_Activated 的时候加载配置信息:Settings = NoteBookSettings.Load();
在Application_Deactivated和Application_Closing的时候保存配置信息:Settings.Save();
下面开始功能的实现:
首先从App中获取配置信息,并且定义一个活动线条的哈希表,定义这个哈希表的原因主要是:因为我们可能使用多个手指在InkPresenter上移动,而我们在InkPresenter中呈现线段是根据用户手指经过的每个点将其加入到线条中最终形成一个完整的线条形成的,这个时候我们需要区分这些点分别是哪个手指触发的,并且应该属于哪条线。而TouchDevice会有一个Id属性用来标志每个手指,这样我们就能通过这个Id来找到每个手指操作的这条线段,我们将其加入到激活线段中,然后就能控制每条线条了,当操作结束将其从哈希表中删除,这样就正常了。
在构造函数中根据配置信息中记录的 当前页面页码 将当前页面的线条恢复,并且恢复其他配置信息。
这里用到了pageNumAndAppBarUpdate方法,该方法用来更新标题页码信息和菜单信息。该方法代码如下:
//更新标题页面和菜单信息
private void pageNumAndAppBarUpdate()
{
pageInfoTextBlock.Text = string.Format("页码信息: {0}/{1}",settings.PageNum+1,settings.StrokeCollections.Count);
nextAppBarButton.IsEnabled=(settings.PageNum <settings.StrokeCollections.Count-1);
backAppBarButton.IsEnabled = (settings.PageNum>=1);
deleteAppBarButton.IsEnabled = (settings.StrokeCollections.Count> 1);
}
构造函数如下:
public MainPage()
{
InitializeComponent();
//初始化InkPresenter
inkPresenter.Strokes = settings.StrokeCollections[settings.PageNum];
inkPresenter.Background =new SolidColorBrush(settings.Background);
//注册触控事件
Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);
//获取ApplicationBarButton
addAppBarButton = this.ApplicationBar.Buttons[0] as ApplicationBarIconButton;
backAppBarButton = this.ApplicationBar.Buttons[1] as ApplicationBarIconButton;
nextAppBarButton = this.ApplicationBar.Buttons[2] as ApplicationBarIconButton;
deleteAppBarButton = this.ApplicationBar.Buttons[3] as ApplicationBarIconButton;
//更新标题处和菜单处
pageNumAndAppBarUpdate();
}
构造函数中要为页面注册FrameReported事件,不然就不能收集手指的输入了,该事件处理函数是该程序关键代码,事件的事件处理如下:
void Touch_FrameReported(object sender, TouchFrameEventArgs e)
{
//获取主触点
TouchPoint primaryTouchPoint = e.GetPrimaryTouchPoint(null);
if (primaryTouchPoint != null && primaryTouchPoint.Action == TouchAction.Down)
{
//禁用主触控点的自动鼠标事件提升,直到所有触控点报告为 Up
// 该方法仅仅可以在是第一次touch屏幕的时候并且只有一个手指touch时候才可以调用
e.SuspendMousePromotionUntilTouchUp();
}
//返回报告的帧中所有 TouchPoint 值的集合。可根据坐标参考点而非根据绝对 Silverlight 坐标计算触控点
TouchPointCollection touchPoints=e.GetTouchPoints(inkPresenter);
foreach (TouchPoint touchPoint in touchPoints)
{
//触点位置,相对于画板左上角
Point pt = touchPoint.Position;
//这个Id是为了区分不同的手指,在从Down到Up整个过程中 每个手指会被分配一个唯一的Id 用来标志他
int id = touchPoint.TouchDevice.Id;
switch (touchPoint.Action)
{
//如果操作是按下,新建一条Stroke,然后给线条设置相关属性,将点加入线段。
//并且记录下这条线段,将其添加到活动线条中。
//如果是移动,将移动中的所有点加入到线条
//手指up的时候将最后一个点加入线条,将其从活动线条中移除
case TouchAction.Down:
Stroke stroke = new Stroke();
stroke.DrawingAttributes.Color = settings.Forground;
stroke.DrawingAttributes.Height=settings.StrokeWidth;
stroke.DrawingAttributes.Width = settings.StrokeWidth;
stroke.StylusPoints.Add(new StylusPoint(pt.X, pt.Y));
inkPresenter.Strokes.Add(stroke);
activeStrokes.Add(id, stroke);
break;
case TouchAction.Move:
activeStrokes[id].StylusPoints.Add(new StylusPoint(pt.X, pt.Y));
break;
case TouchAction.Up:
activeStrokes[id].StylusPoints.Add(new StylusPoint(pt.X, pt.Y));
activeStrokes.Remove(id);
break;
}
}
}
这样就可以在页面中画东西了。
下面是实现添加页面,前后翻页,删除页面,这几个都是使用ApplicationBar控制的,几个处理函数如下:
//增加一个新页
private void addAppBarButton_Click(object sender, EventArgs e)
{
StrokeCollection strokes = new StrokeCollection();
settings.PageNum += 1;
settings.StrokeCollections.Insert(settings.PageNum,strokes);
inkPresenter.Strokes=strokes;
pageNumAndAppBarUpdate();
}
//上一页
private void backAppBarButton_Click(object sender, EventArgs e)
{
if (settings.PageNum>=1)
{
settings.PageNum -= 1;
inkPresenter.Strokes = settings.StrokeCollections[settings.PageNum];
pageNumAndAppBarUpdate();
}
else
{
MessageBox.Show("当前已经是第一页","记事本",MessageBoxButton.OK);
}
}
//下一页
private void nextAppBarButton_Click(object sender, EventArgs e)
{
if(settings.PageNum<settings.StrokeCollections.Count-1)
{
settings.PageNum += 1;
inkPresenter.Strokes = settings.StrokeCollections[settings.PageNum];
pageNumAndAppBarUpdate();
}
else
{
MessageBox.Show("当前已经是最后一页","记事本",MessageBoxButton.OK);
}
}
//删除当前页
private void deleteAppBarButton_Click(object sender, EventArgs e)
{
MessageBoxResult result = MessageBox.Show("您确定要删除这页吗?", "记事本",MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
if (settings.StrokeCollections.Count == 1)
{
settings.StrokeCollections[0].Clear();
}
else
{
settings.StrokeCollections.RemoveAt(settings.PageNum);
if (settings.PageNum == settings.StrokeCollections.Count)
{
settings.PageNum -= 1;
}
inkPresenter.Strokes=settings.StrokeCollections[settings.PageNum];
}
pageNumAndAppBarUpdate();
}
}
还有最后一个就是背景色的切换和 画笔粗细的设置,都很简单,处理函数如下:
//设置画笔粗细
private void setStrokeWidth_Click(object sender, EventArgs e)
{
ApplicationBarMenuItem item = sender as ApplicationBarMenuItem;
if (item.Text == "细线")
{
settings.StrokeWidth = 1;
}
else if (item.Text == "中线")
{
settings.StrokeWidth = 2;
}
else if (item.Text == "粗线")
{
settings.StrokeWidth = 3;
}
}
OK,这就基本上Ok了,截两张图看看:
这里实现功能还不够完善,还有些内容比如橡皮擦什么的还有待添加。
参考资料Programing Windows Phone 7。