Introduction
The first article I wrote on this app idea was basically just a teaser
with some basic ideas, and I've learned a lot of new things over the
last month that I would like to share.
First article: http://www.codeproject.com/Articles/478645/Switch-To-SMS-Text-Event-Reminders-When-Your-Ultra
Overview
What I'm going to do is to give code examples of things that I found
interesting or useful as I built the app. As you might have seen from
the first article, this app is a C# desktop app.
First I must
apologize, I got tons of help from snippets of code all over the web
& since I found a lot of them that didn't work and stopped looking
once I found something that worked, I didn't always keep track of where I
got the pieces that worked. If you recognize your code snippet in here
somewhere, thank you very much and I'm sorry for not attributing it to
you.
Here are the elements we're going to look at
Code Examples
In case of the first example, I do have the original reference though:
http://blogs.msdn.com/b/delay/archive/2009/08/31/get-out-of-the-way-with-the-tray-minimize-to-tray-sample-implementation-for-wpf.aspx
Minimizing to Tray
This was a pretty cool piece of code, to minimize your application to the tray, just add this class:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
namespace SuspendReminder
{
public static class MinimizeToTray
{
public static void Enable(Window window)
{
new MinimizeToTrayInstance(window);
}
private class MinimizeToTrayInstance
{
private Window _window;
private NotifyIcon _notifyIcon;
private bool _balloonShown;
public MinimizeToTrayInstance(Window window)
{
Debug.Assert(window != null, "window parameter is null.");
_window = window;
_window.StateChanged += new EventHandler(HandleStateChanged);
}
private void HandleStateChanged(object sender, EventArgs e)
{
if (_notifyIcon == null)
{
_notifyIcon = new NotifyIcon();
_notifyIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location);
_notifyIcon.MouseClick += new MouseEventHandler(HandleNotifyIconOrBalloonClicked);
_notifyIcon.BalloonTipClicked += new EventHandler(HandleNotifyIconOrBalloonClicked);
}
_notifyIcon.Text = _window.Title;
var minimized = (_window.WindowState == WindowState.Minimized);
_window.ShowInTaskbar = !minimized;
_notifyIcon.Visible = minimized;
if (minimized && !_balloonShown)
{
_notifyIcon.ShowBalloonTip(1000, null, _window.Title, ToolTipIcon.None);
_balloonShown = true;
}
}
private void HandleNotifyIconOrBalloonClicked(object sender, EventArgs e)
{
_window.WindowState = WindowState.Normal;
}
}
}
}
Make sure you add an instance of it, e.g.:
private System.Windows.Forms.NotifyIcon MyNotifyIcon;
Then call it:
MinimizeToTray.Enable(this);
This is what it should look like:
private System.Windows.Forms.NotifyIcon MyNotifyIcon;
public MainWindow()
{
InitializeComponent();
MinimizeToTray.Enable(this);
That's pretty much all that is to it, very cool.
For my app I need to detect suspend and resume events, this also turned out to be pretty simple.
Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
Then just add this:
void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
{
String pos = getGeo();
if (e.Mode == Microsoft.Win32.PowerModes.Suspend)
{
richTextBox1.AppendText("Suspending!\n");
}
else if (e.Mode == Microsoft.Win32.PowerModes.Resume)
{
richTextBox1.AppendText("Resumed.\n");
}
}
In this example I'm just adding the events to a
RichTextBox, but you could add almost any event you like, in my case I'm
actually adding a call back to a web server to start watching for
events.
This
was a pretty useful piece of code for me as well, if you need to save
variable states across application launches. To get this to work,
right-click on your project and select 'Properties', then click on
'Settings' and add a key value and type to whatever you want to store.
Now we can start using this inside the application, this is what I added just after initialization:
String pin = SuspendReminder.Properties.Settings.Default.pin;
I had saved a slot for 'pin' and therefore I can now reference it and if I needed to save it, this is all you need:
SuspendReminder.Properties.Settings.Default.pin = tbPIN.Text;
There are other ways of doing this, but I simply added a call to:
using System.Device.Location;
Then use this piece of code to get your location:
String getGeo()
{
String geo = "";
if (cbGeo.IsChecked == true)
{
var watcher = new GeoCoordinateWatcher();
watcher.TryStart(true, TimeSpan.FromMilliseconds(3000));
var coord = watcher.Position.Location;
geo = coord.Latitude + "," + coord.Longitude;
}
return geo;
}
This is all you need to get longitude and latitude values (obviously if your computer supports it, like an Ultrabook)
This was another neat piece of code, I needed to post some data to a web server, turned out to be a piece of cake:
private void sendData(String data1, String data2, String data3)
{
try
{
string URLAuth = "http://www.mydomain.com/send-data-here";
WebClient webClient = new WebClient();
NameValueCollection formData = new NameValueCollection();
formData["data1"] = data1;
formData["data2"] = data2;
formData["data3"] = data3;
byte[] responseBytes = webClient.UploadValues(URLAuth, "POST", formData);
string result = Encoding.UTF8.GetString(responseBytes);
webClient.Dispose();
lError.Content = "";
}
catch (Exception ex)
{
lError.Content = "There has been an internet connection error.";
}
}
If you're completely new to all this, simply use it like this:
sendData("pizza", "popcorn", "drinks");
This
was one of the most important parts of my application, I needed to sign
in to Windows Live Connect services since I needed access to user
calendar events. Getting this to work along with getting a refresh token
to use in subsequent REST calls was the most difficult. Now that I've
got the code, it looks real easy, getting to this point was pretty
painful since almost all the code examples are for Windows RT/ Windows
Store/ Metro / Modern apps and not Desktop apps.
This is your first step:
Get your app registered here - https://manage.dev.live.com/Applications/Index
Now,
in the API Settings, first specify your app to be a 'Mobile Client
App', but also specify a 'Redirect Domain' - make sure that checking the
'mobile' option doesn't clear out the redirect domain. I needed both
since I wanted to sign the user in with the application, but then send
updates via the server-side and for that I needed a REST implementation.
So what basically happens here is this. The app sends a request to Windows Live using a WebClient, this fires up the browser form and allows you to see the Windows Live sign-in page inside the app. You then authenticate the app to use your calendar data and then the browser goes away.
The app's interface then changes to indicate that you have signed in and allows you to either simply minimize or hit the 'Hide' button, this launches the code to make the app pop into the tray. Not all apps should go in the tray, but in this case it's ideal.
In the background what's happened is that the app have received a refresh token from Windows Live, this is important since the access code you've received is only valid for an hour - pretty useless for the purposes of this app. The app then passes the refresh token on to the web server, along with your mobile phone number so that it can keep track of which refresh token belongs to whom. This is important since when your ultrabook/slate/laptop goes into suspend mode, we'll notify the server and it will then have to use the refresh token to get new access and authorization tokens to actually send the updates. So let's get back to coding.
This is the application side code in your main window:
static string client_id = "your-client-id";
static string client_secret = "your-client-secret";
static string accessTokenUrl =
String.Format(@"https://login.live.com/oauth20_token.srf?client_id={0}&client" +
@"_secret={1}&redirect_uri=https://login.live.com/oauth20_desktop.srf&grant_type" +
@"=authorization_code&code=", client_id, client_secret);
static string apiUrl = @"https://apis.live.net/v5.0/";
public Dictionary<string, string> tokenData = new Dictionary<string, string>();
You'll get your Client ID and Client Secret when you have registered your app in the step above.
This is the bulk of the code:
private void getAccessToken()
{
if (App.Current.Properties.Contains("auth_code"))
{
makeAccessTokenRequest(accessTokenUrl + App.Current.Properties["auth_code"]);
}
}
private void makeAccessTokenRequest(string requestUrl)
{
try
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(accessToken_DownloadStringCompleted);
wc.DownloadStringAsync(new Uri(requestUrl));
lError.Content = "";
}
catch (Exception ex)
{
lError.Content = "There has been an internet connection error.";
}
}
void accessToken_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
tokenData = deserializeJson(e.Result);
if (tokenData.ContainsKey("access_token"))
{
App.Current.Properties.Add("access_token", tokenData["access_token"]);
App.Current.Properties.Add("refresh_token", tokenData["refresh_token"]);
getUserInfo();
}
}
private Dictionary<string, string> deserializeJson(string json)
{
var jss = new JavaScriptSerializer();
var d = jss.Deserialize<Dictionary<string, string>>(json);
return d;
}
private void getUserInfo()
{
if (App.Current.Properties.Contains("access_token"))
{
try
{
makeApiRequest(apiUrl + "me?access_token=" + App.Current.Properties["access_token"]);
lError.Content = "";
}
catch (Exception ex)
{
lError.Content = "There has been an internet connection error.";
}
}
}
private void makeApiRequest(string requestUrl)
{
try
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
wc.DownloadStringAsync(new Uri(requestUrl));
lError.Content = "";
}
catch (Exception ex)
{
lError.Content = "There has been an internet connection error.";
}
}
void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
changeView(e.Result);
}
private void changeView(string result)
{
string imgUrl = apiUrl + "me/picture?access_token=" + App.Current.Properties["access_token"];
imgUser.Source = new BitmapImage(new Uri(imgUrl, UriKind.RelativeOrAbsolute));
String code = "" + App.Current.Properties["refresh_token"];
String auth = "" + App.Current.Properties["access_token"];
}
void browser_Closed(object sender, EventArgs e)
{
try
{
getAccessToken();
lError.Content = "";
}
catch (Exception ex)
{
lError.Content = "There has been an internet connection error.";
}
}
You will see a reference to a browser in there, this
is because the Live Connect authorization process in our case depends on
signing in via a web browser interface, this is the code you will need
for the browser:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Text.RegularExpressions;
namespace WpfApplication3
{
public partial class BrowserWindow : Window
{
static string scope = "wl.signin wl.calendars wl.offline_access wl.contacts_calendars";
static string client_id = "your-client-id";
static Uri signInUrl = new Uri(String.Format(@"https://login.live.com/oauth20" +
@"_authorize.srf?client_id={0}&redirect_uri=https://login.live.com/" +
@"oauth20_desktop.srf&response_type=code&scope={1}", client_id, scope));
MainWindow mainWindow = new MainWindow();
public BrowserWindow()
{
InitializeComponent();
webBrowser.Navigate(signInUrl);
}
private void webBrowser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
if (e.Uri.AbsoluteUri.Contains("code="))
{
if (App.Current.Properties.Contains("auth_code"))
{
App.Current.Properties.Clear();
}
string auth_code = Regex.Split(e.Uri.AbsoluteUri, "code=")[1];
App.Current.Properties.Add("auth_code", auth_code);
this.Close();
}
}
}
}
Do not forget to add your Client ID here too (like I did...)
The XAML for the browser looks like this:
<Window x:Class="WpfApplication3.BrowserWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Sign In" Height="460" Width="423.881"
ShowInTaskbar="False" WindowStartupLocation="CenterScreen">
<Grid>
<WebBrowser Height="419" HorizontalAlignment="Left"
Name="webBrowser" VerticalAlignment="Top"
Width="406" LoadCompleted="webBrowser_LoadCompleted" />
</Grid>
</Window>
For
the server-side, I'm using PHP, partially since I'm familiar with it
& also because it's what I have on my hosted web server.
This is the code referenced in the redirect domain I referred to earlier:
<?php
define('AUTHCOOKIE', 'wl_auth');
define('ERRORCODE', 'error');
define('ERRORDESC', 'error_description');
define('ACCESSTOKEN', 'access_token');
define('AUTHENTICATION_TOKEN', 'authentication_token');
define('CODE', 'code');
define('SCOPE', 'scope');
define('EXPIRESIN', 'expires_in');
define('REFRESHTOKEN', 'refresh_token');
define('CLIENTID', 'your-client-id');
define('CLIENTSECRET', 'your-client-secret');
define('CALLBACK', 'http://your-domain-and-reference-to-this-file');
define('OAUTHURL', 'https://login.live.com/oauth20_token.srf');
This call is to get the actual calendar events - the curl_getevents.php file will be shown next
if (isset($getupdate)) { $url = 'http://your-domain/curl_getevents.php?cust_id='.$cust_id;
$ch = curl_init();
// Set query data here with the URL
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, '3');
$content = trim(curl_exec($ch));
curl_close($ch); }
function buildQueryString($array)
{
$result = '';
foreach ($array as $k => $v)
{
if ($result == '')
{
$prefix = '';
}
else
{
$prefix = '&';
}
$result .= $prefix . rawurlencode($k) . '=' . rawurlencode($v);
}
return $result;
}
function parseQueryString($query)
{
$result = array();
$arr = preg_split('/&/', $query);
foreach ($arr as $arg)
{
if (strpos($arg, '=') !== false)
{
$kv = preg_split('/=/', $arg);
$result[rawurldecode($kv[0])] = rawurldecode($kv[1]);
}
}
return $result;
}
function sendRequest(
$url,
$method = 'GET',
$data = array(),
$headers = array('Content-type: application/x-www-form-urlencoded;charset=UFT-8'))
{
$context = stream_context_create(array
(
'http' => array(
'method' => $method,
'header' => $headers,
'content' => buildQueryString($data)
)
));
return file_get_contents($url, false, $context);
}
function requestAccessToken($content)
{
$response = sendRequest(
OAUTHURL,
'POST',
$content);
if ($response !== false)
{
$authToken = json_decode($response);
if (!empty($authToken) && !empty($authToken->{ACCESSTOKEN}))
{
return $authToken;
}
}
return false;
}
function requestAccessTokenByVerifier($verifier)
{
return requestAccessToken(array(
'client_id' => CLIENTID,
'redirect_uri' => CALLBACK,
'client_secret' => CLIENTSECRET,
'code' => $verifier,
'grant_type' => 'authorization_code'
));
}
function requestAccessTokenByRefreshToken($refreshToken)
{
return requestAccessToken(array(
'client_id' => CLIENTID,
'redirect_uri' => CALLBACK,
'client_secret' => CLIENTSECRET,
'refresh_token' => $refreshToken,
'grant_type' => 'refresh_token'
));
}
function handlePageRequest()
{
if (!empty($_GET[ACCESSTOKEN]))
{
// There is a token available already. It should be the token flow. Ignore it.
return;
}
$verifier = $_GET[CODE];
if (!empty($verifier))
{
$token = requestAccessTokenByVerifier($verifier);
if ($token !== false)
{
handleTokenResponse($token);
}
else
{
handleTokenResponse(null, array(
ERRORCODE => 'request_failed',
ERRORDESC => 'Failed to retrieve user access token.'));
}
return;
}
$refreshToken = readRefreshToken();
if (!empty($refreshToken))
{
$token = requestAccessTokenByRefreshToken($refreshToken);
if ($token !== false)
{
handleTokenResponse($token);
}
else
{
handleTokenResponse(null, array(
ERRORCODE => 'request_failed',
ERRORDESC => 'Failed to retrieve user access token.'));
}
return;
}
$errorCode = $_GET[ERRORCODE];
$errorDesc = $_GET[ERRORDESC];
if (!empty($errorCode))
{
handleTokenResponse(null, array(
ERRORCODE => $errorCode,
ERRORDESC => $errorDesc));
}
}
function readRefreshToken()
{
// read refresh token of the user identified by the site.
return null;
}
function saveRefreshToken($refreshToken)
{
// save the refresh token and associate it with the user identified by your site credential system.
}
function handleTokenResponse($token, $error = null)
{
$authCookie = $_COOKIE[AUTHCOOKIE];
$cookieValues = parseQueryString($authCookie);
if (!empty($token))
{
$cookieValues[ACCESSTOKEN] = $token->{ACCESSTOKEN};
$cookieValues[AUTHENTICATION_TOKEN] = $token->{AUTHENTICATION_TOKEN};
$cookieValues[SCOPE] = $token->{SCOPE};
$cookieValues[EXPIRESIN] = $token->{EXPIRESIN};
if (!empty($token->{ REFRESHTOKEN }))
{
saveRefreshToken($token->{ REFRESHTOKEN });
}
}
if (!empty($error))
{
$cookieValues[ERRORCODE] = $error[ERRORCODE];
$cookieValues[ERRORDESC] = $error[ERRORDESC];
}
setrawcookie(AUTHCOOKIE, buildQueryString($cookieValues), 0, '/', $_SERVER[SERVER_NAME]);
}
handlePageRequest();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:msgr="http://messenger.live.com/2009/ui-tags">
<head>
<title>Live SDK Callback Page</title>
<script src="http://www.codeproject.com/js.live.net/v5.0/wl.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
This is the piece where we get to look at the curl_getevents.php code:
<?php
$cust_id = $_REQUEST['cust_id']; $refreshtoken = $_REQUEST['refreshtoken'];
$ch = curl_init();
$url = 'https://login.live.com/oauth20_token.srf';
$body = "client_id=your-client-id&redirect_uri=http://your-domain" +
"-and-callback-file.php&client_secret=your-client" +
"-secret&refresh_token=$refreshtoken&grant_type=refresh_token";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/x-www-form-urlencoded',
'Content-Length: ' . strlen($body))
);
$result = curl_exec($ch);
$obj=json_decode($result);
$url = 'https://apis.live.net/v5.0/me/events?access_token='.$obj->access_token;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, '3');
$content = trim(curl_exec($ch));
curl_close($ch);
$events = json_decode($content, TRUE);
foreach($events['data'] as $key=>$val){
$name = $val['name'];}
?>
That's it! With the code above, you should be able to set up a
CRON job on your Apache web server to execute the CURL code to retrieve
the Windows Live Connect user details you need by using the Refresh
token to get a new Access and Authorization token.
References
Almost forgot, this helped me when someone else posted it, these are all the references I used,
in case you get stuck:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Web.Script.Serialization;
using System.Net;
using System.IO;
using System.Collections.Specialized;
using System.IO.IsolatedStorage;
using System.Timers;
using System.Threading;
using System.Device.Location;
using System.ComponentModel;
using SuspendReminder;
The End
I
really wanted to write a bit more detailed explanations of each piece
of code, but it has turned into a pretty long article already. Hopefully
I didn't leave anything out. All of the code here is from my working
code, so it should all work.
Happy coding!!