Download ReminderWinForms-noexe.zip
Download ReminderWinForms.zip
Download Memo_01042014-noexe.zip
Download Memo_01042014.zip
Download Memo_no_NewtonPackage_noBin-noexe.zip
Introduction
This app I try to realize as Windows Store App, so some new and specific things was used. List of interesting poinst:
- DataConvertors
- DataBindings
- JSON usage
- XAML
- Layout for different device orientation (VisualStateManager)
- Async methods
- File usage (writing, reading)
- Image usage
- Toasts
- BackgroundTasks
- etc.
Background
I start to learn .Net a half year ago as a hobby, and first programm was Reminder in WinForms, now I try to do same things but in Windows Store App.
Basic idea for this app - create programm that allow in easy way add, edit, remove, search and remind different events. As was mentioned earlier, first version of my trying i create in WinForms (now i can see a lot of problems ther, errors, possibility for improvements and other thins - add to attachment varian in WinForms with name "eminderWinForms.zip").
Hope in nearest feature I'll look to this App and found possibility for improvement (some of them i know now - what need to do I place at the end of this Article).
Using the code and points of interest
So, lets look at the heart of my App.
I have few pages for different purposes :
- MainPage
- SearchPage
- EditPage
- AddEventPage
Also created CommonRuntimeComponent for BackgroundTask.
All pages I placed in same namespace - Memo, BackgroundTask - Tasks.
A lot of work was done in Xaml pages:
added few styles, created different Visual states. For me it's very interesting was to create VIsualStates using VisualStateManages. So I found 2 ways to do this:
- Create new VisualState and new layout - take a lot of time, but result - exactly what you need. Some times it can cause problem if you want to use controls in your c# code - due to different contols names.
- Modify properties of each Control - as result - much faster creating and no problem with Controls usage.
- Also you can combine this 2 methods.
Example (if you have few views of layout - just choose what exectly need to show)
<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>
Also for correct working of this code you need to create refered c# code in your SizeChanged event of Page. Something like :
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);
}
}
Same principle was used for all pages, as result - I got different layout for my app in different positions:
Also very interesting for me was creating a DataConverter. I create few of them for different purpose - DateConverter, TimeConverter, PicturePathConverter, etc. Main principle - just implement IValueConverter. As sample - code for PicturePathConverter
class PicturePathConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
string filePath = value.ToString();
BitmapImage image = new BitmapImage();
if (filePath.Contains("Assets"))
{
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 - cool things that allow to show what you do even during designing stage. In additional using of this sometimes is very simple - just make few clicks, in other situation you need to do a liitle bit more. As sample of Binding - place part of code that allow to show my events from JSON file:
Xaml:
add namesapce for data usage
xmlns:data="using:Memo.Data"
Create ViewSource and that use it in your layout
<CollectionViewSource
x:Name="itemsViewSource"
Source="{Binding Events}"
d:Source="{Binding Events, Source={d:DesignData Source=/DataModel/EventsData.json, Type=data:GroupOfEvents}}"/>
Result:
New for me was JSON file - spend few days for reading about what it is and how i can use it, but after understanding and some trials - all come much more easy - just create object and use it. I store data in Json File. Sample of it:
{"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"
}
]
}
For reading such file i Use next code:
public static async Task<ObservableCollection<Event>> ReadFromJsonFileAsync(ObservableCollection<Event> events)
{
StorageFolder localFolder = ApplicationData.Current.LocalFolder;
if (await Extensions.CheckFileExistingByName(Extensions.fileToSaveEvents))
{
StorageFile originalFile = await localFolder.GetFileAsync(Extensions.fileToSaveEvents);
using (IRandomAccessStream textStream = await originalFile.OpenReadAsync())
{
using (DataReader textReader = new DataReader(textStream))
{
uint textLength = (uint)textStream.Size;
await textReader.LoadAsync(textLength);
string jsonContents = textReader.ReadString(textLength);
JsonObject jsonObject = JsonObject.Parse(jsonContents);
JsonArray jsonArray = jsonObject["Events"].GetArray();
foreach (JsonValue jsonValue in jsonArray)
{
JsonObject eventObject = jsonValue.GetObject();
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 events;
}
A lot of time I spend for BackgroundTask - after reading MSDN article i think that it's easy - create Common Runtime Component and register task, but after some trial I found few limitation and so - spend some time for breaking it (if be hones - in this app still improvement for usage Background Task can be done). I try to understand how it's work and how it's can be used - result - every 15 min i check lst of events, and if it's exist - show toasts like :
Also for implementing BackgroundTask you need to do next main things:
- Create it in separate CRC
- implement IBackgroundTask
- class that implement IBackgroundTask must be sealed
- if you use Async method - use deferal
- create reference in your project for this component
- register your task (add trigger - don't forget it). If you want to make timer - your app must realize Toast and notification, and must be placed on lockScreen
- register your task in Package.appxmanifest
- debug (just place stop point in RUN method of your class that implement IBackgroundTask)
Also some usefull samples and explanation can be found at MSDN. Like this one.
Think this is the most interesting points for me, that i currently investigate.
Points for improvement and known issues
Of couse ther is a lot of work that can be done more:
- LifeCycle management
- Share support
- Implement IDisposable for my entity class (event)
- Push notification
- Settings
- Spalsh screen
- etc
Also i want to say few words about know issues:
- If you work with a big amount of events sometimes you can get Unhandled Win32 Exception
- BackgroundTask sometimes not fire (currently have no root cause for this)
History
This is the first published version of app.