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

提醒 - 简单的 Windows 应用商店应用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (6投票s)

2014年4月2日

CPOL

4分钟阅读

viewsIcon

17576

downloadIcon

1695

提醒:简单的 Windows 应用商店应用。在此应用中,我尝试使用 Windows 8 应用的一些特定功能。结果是我获得了良好的体验,现在我想展示所做的工作。

下载 ReminderWinForms-noexe.zip

下载 ReminderWinForms.zip

下载 Memo_01042014-noexe.zip

下载 Memo_01042014.zip

下载 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 非常有趣。所以我找到了两种方法来实现这一点:

  1. 创建新的 VisualState 和新的布局——这需要很多时间,但结果——正是你想要的。有时如果你想在 C# 代码中使用控件,可能会出现问题,因为控件名称不同。
  2. 修改每个控件的属性——结果——创建速度更快,并且在使用控件时没有问题。
  3. 你也可以结合这两种方法。

示例(如果你有几种布局视图——只需选择你确切需要显示的内容)

        <!--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

此外,我想说几句关于已知问题。

  1. 如果你处理大量事件,有时你可能会遇到未处理的 Win32 异常。
  2. BackgroundTask 有时不触发(目前没有根本原因)。

历史

这是应用发布的第一个版本。

© . All rights reserved.