提醒 - 简单的 Windows 应用商店应用
提醒:简单的 Windows 应用商店应用。在此应用中,我尝试使用 Windows 8 应用的一些特定功能。结果是我获得了良好的体验,现在我想展示所做的工作。
下载 Memo_no_NewtonPackage_noBin-noexe.zip
引言
这个应用我尝试将其实现为 Windows 应用商店应用,因此使用了一些新的特定功能。有趣的点列表:
- 数据转换器
- 数据绑定
- JSON 用法
- XAML
- 不同设备方向的布局(VisualStateManager)
- 异步方法
- 文件使用(写入、读取)
- 图片使用
- Toast 通知
- 后台任务
- 等等。
背景
我大约半年前开始作为业余爱好学习 .Net,第一个程序是 WinForms 中的 Reminder,现在我尝试在 Windows 应用商店应用中做同样的事情。
这个应用的基本理念是创建一个程序,可以轻松地添加、编辑、删除、搜索和提醒不同的事件。正如之前提到的,我尝试的第一个版本是在 WinForms 中创建的(现在我可以看到很多问题、错误、改进的可能性以及其他事情——将 WinForms 中的变体添加到附件中,名称为“eminderWinForms.zip”)。
希望在不久的将来,我会看看这个应用并找到改进的可能性(我现在知道其中一些——我将在本文末尾列出需要做的事情)。
使用代码和关注点
那么,让我们看看我的应用的精髓。
我为不同的目的设置了几个页面:
- MainPage
- 搜索页面
- 编辑页面
- 添加事件页面
还为 BackgroundTask 创建了 CommonRuntimeComponent。
所有页面我都放在同一个命名空间中——Memo,BackgroundTask——Tasks。
在 Xaml 页面上做了大量工作。
添加了一些样式,创建了不同的 Visual 状态。对我来说,使用 VisualStateManages 创建 VisualStates 非常有趣。所以我找到了两种方法来实现这一点:
- 创建新的 VisualState 和新的布局——这需要很多时间,但结果——正是你想要的。有时如果你想在 C# 代码中使用控件,可能会出现问题,因为控件名称不同。
- 修改每个控件的属性——结果——创建速度更快,并且在使用控件时没有问题。
- 你也可以结合这两种方法。
示例(如果你有几种布局视图——只需选择你确切需要显示的内容)
<!--Visual state manager-->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="defaultLayout">
</VisualState>
<VisualState x:Name="snappedLayout">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="defaultView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="portraitView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="snappedView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="narrowView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
此外,为了使此代码正常工作,您需要在 Page 的 SizeChanged 事件中创建引用的 C# 代码。类似于
private void pageRoot_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width < 500 && (e.NewSize.Height > e.NewSize.Width) && e.NewSize.Width > 320)
{
VisualStateManager.GoToState(this, "narrowLayout", true);
}
else if (e.NewSize.Width > 500 && (e.NewSize.Height > e.NewSize.Width))
{
VisualStateManager.GoToState(this, "portraitLayout", true);
}
else if (e.NewSize.Width > 320)
{
VisualStateManager.GoToState(this, "defaultLayout", true);
}
else
{
VisualStateManager.GoToState(this, "snappedLayout", true);
}
}
所有页面都使用了相同的原理,结果——我在不同的位置为我的应用获得了不同的布局。
对我来说,创建 DataConverter 也非常有意思。我为不同目的创建了几个——DateConverter、TimeConverter、PicturePathConverter 等。主要原理——只需实现 IValueConverter。作为示例——PicturePathConverter 的代码。
class PicturePathConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
string filePath = value.ToString(); //get path to picture
BitmapImage image = new BitmapImage(); //create bitmap image blank
if (filePath.Contains("Assets")) //if standart picture
{
Extensions.LoadImageFromAssetsLibrary(image, filePath);
return image;
}
else
{
Extensions.LoadImageFromString(image, filePath);
return image;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
DataBinding——这个很酷的东西可以让你在设计阶段就看到你所做的一切。此外,使用它有时非常简单——只需点击几下,在其他情况下你需要多做一点。作为 Binding 的示例——放置允许我从 JSON 文件显示事件的代码片段。
Xaml
添加用于数据使用的命名空间。
xmlns:data="using:Memo.Data"
创建 ViewSource,然后在布局中使用它。
<CollectionViewSource
x:Name="itemsViewSource"
Source="{Binding Events}"
d:Source="{Binding Events, Source={d:DesignData Source=/DataModel/EventsData.json, Type=data:GroupOfEvents}}"/>
结果
对我来说,JSON 文件是新的——花了几天时间阅读它是什么以及我如何使用它,但在理解和一些尝试后——一切都变得容易多了——只需创建一个对象并使用它。我将数据存储在 Json 文件中。这是示例。
{"Events":
[
{
"UniqueId": "Day-1-Item-1",
"Name": "Event 1",
"Place": "Item Subtitle: 1",
"Description": "Event 1",
"Start" : "2,25,2014,2,14",
"End" : "2,25,2014,16,44",
"ImagePath" : "Assets/appbar.calendar.png"
},
{
"UniqueId": "Event 2",
"Name": "Item Title: 1",
"Place": "Item Subtitle: 1",
"Description": "Event 2",
"Start" : "2,25,2014,2,14",
"End" : "2,25,2014,16,44",
"ImagePath" : "Assets/appbar.calendar.png"
}
]
}
为了读取这样的文件,我使用了以下代码。
public static async Task<ObservableCollection<Event>> ReadFromJsonFileAsync(ObservableCollection<Event> events)
{
//create folder
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
//cehck existance of file
if (await Extensions.CheckFileExistingByName(Extensions.fileToSaveEvents))
{
//create file
StorageFile originalFile = await localFolder.GetFileAsync(Extensions.fileToSaveEvents);
//read file
using (IRandomAccessStream textStream = await originalFile.OpenReadAsync())
{
// Read text stream
using (DataReader textReader = new DataReader(textStream))
{
//get size
uint textLength = (uint)textStream.Size;
await textReader.LoadAsync(textLength);
// read it
string jsonContents = textReader.ReadString(textLength);
//create Json object for converting readed string
JsonObject jsonObject = JsonObject.Parse(jsonContents);
JsonArray jsonArray = jsonObject["Events"].GetArray();
//read each string
foreach (JsonValue jsonValue in jsonArray)
{
JsonObject eventObject = jsonValue.GetObject();
//add each event to groups with day
events.Add(new Event(eventObject["UniqueId"].GetString(),
eventObject["Name"].GetString(),
eventObject["Place"].GetString(),
eventObject["Description"].GetString(),
eventObject["Start"].GetString(),
eventObject["End"].GetString(),
eventObject["ImagePath"].GetString()
));
}
}
}
}
//return collection of exisisting events in file
return events;
}
我花了大量时间在 BackgroundTask 上——阅读 MSDN 文章后,我认为这很简单——创建一个 Common Runtime Component 并注册任务,但在一些尝试后,我发现了一些限制,因此——花了一些时间来克服它(说实话——在这个应用中,后台任务的使用仍然可以改进)。我尝试理解它的工作原理以及如何使用它——结果——我每 15 分钟检查一次事件列表,如果存在——则显示 Toast,例如:
此外,要实现 BackgroundTask,你需要做以下主要事情:
- 在单独的 CRC 中创建它。
- 实现 IBackgroundTask。
- 实现 IBackgroundTask 的类必须是密封的。
- 如果你使用 Async 方法——使用 deferal。
- 为这个组件在你的项目中创建引用。
- 注册你的任务(添加触发器——不要忘记它)。如果你想制作一个计时器——你的应用必须实现 Toast 和通知,并且必须放在锁屏界面上。
- 在 Package.appxmanifest 中注册你的任务。
- 调试(只需在实现 IBackgroundTask 的类的 RUN 方法中放置断点)。
此外,你还可以在 MSDN 上找到一些有用的示例和解释。比如这个链接。
我认为这是我目前正在研究的最有趣的点。
改进点和已知问题
当然,还有很多工作可以做得更好。
- 生命周期管理
- 共享支持
- 为我的实体类(事件)实现 IDisposable。
- 推送通知
- 设置
- 启动屏幕
- etc
此外,我想说几句关于已知问题。
- 如果你处理大量事件,有时你可能会遇到未处理的 Win32 异常。
- BackgroundTask 有时不触发(目前没有根本原因)。
历史
这是应用发布的第一个版本。