Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Switch to SMS/Text Event Reminders When Your Ultrabook is in Suspend Mode #2

0.00/5 (No votes)
28 Nov 2012 1  
This is a follow-on article from my first on the topic of sending text messages as event reminders.

This article is an entry in our AppInnovation Contest. Articles in this sub-section are not required to be full articles so care should be taken when voting.

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
{
    /// Class implementing support for "minimize to tray" functionality.
    public static class MinimizeToTray
    {
        /// Enables "minimize to tray" behavior for the specified Window.
        public static void Enable(Window window)
        {
            // No need to track this instance; its event handlers will keep it alive
            new MinimizeToTrayInstance(window);
        }

        /// Class implementing "minimize to tray" functionality for a Window instance.
        private class MinimizeToTrayInstance
        {
            private Window _window;
            private NotifyIcon _notifyIcon;
            private bool _balloonShown;

            /// Initializes a new instance of the MinimizeToTrayInstance class.
            public MinimizeToTrayInstance(Window window)
            {
                Debug.Assert(window != null, "window parameter is null.");
                _window = window;
                _window.StateChanged += new EventHandler(HandleStateChanged);
            }

            /// Handles the Window's StateChanged event.
            private void HandleStateChanged(object sender, EventArgs e)
            {
                if (_notifyIcon == null)
                {
                    // Initialize NotifyIcon instance "on demand"
                    _notifyIcon = new NotifyIcon();
                    _notifyIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location);
                    _notifyIcon.MouseClick += new MouseEventHandler(HandleNotifyIconOrBalloonClicked);
                    _notifyIcon.BalloonTipClicked += new EventHandler(HandleNotifyIconOrBalloonClicked);
                }
                // Update copy of Window Title in case it has changed
                _notifyIcon.Text = _window.Title;

                // Show/hide Window and NotifyIcon
                var minimized = (_window.WindowState == WindowState.Minimized);
                _window.ShowInTaskbar = !minimized;
                _notifyIcon.Visible = minimized;
                if (minimized && !_balloonShown)
                {
                    // If this is the first time minimizing to the tray, show the user what happened
                    _notifyIcon.ShowBalloonTip(1000, null, _window.Title, ToolTipIcon.None);
                    _balloonShown = true;
                }
            }


            /// Handles a click on the notify icon or its balloon.
            private void HandleNotifyIconOrBalloonClicked(object sender, EventArgs e)
            {
                // Restore the Window
                _window.WindowState = WindowState.Normal;
            }
        }
    }
}

Make sure you add an instance of it, e.g.:

private System.Windows.Forms.NotifyIcon MyNotifyIcon;

Then call it:

// Enable "minimize to tray" behavior for this Window
MinimizeToTray.Enable(this);

This is what it should look like:

private System.Windows.Forms.NotifyIcon MyNotifyIcon;
 
    public MainWindow()
    {
        InitializeComponent();

        // Enable "minimize to tray" behavior for this Window
        MinimizeToTray.Enable(this);

That's pretty much all that is to it, very cool.

Suspend and Resume Events 

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.

Saving Application Properties 

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; 

Geolocation  

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)

HTTP Posting Data  

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"); 

Windows Live Connect 

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');
 
// Update the following values
define('CLIENTID', 'your-client-id');
define('CLIENTSECRET', 'your-client-secret');
 

// Make sure this is identical to the redirect_uri parameter passed in WL.init() call.
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'];
//in my app I'm saving the refreshtoken in a database, for for simplicity's sake
// here I'm just creating a variable for it that could be passed via a POST or GET request 

$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";             
//Note the client secret, client ID and refresh token above as well
// as your callback file specified on the Windows Live Connect site

$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))                                                                       
);  
 
//execute post
$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();
// 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);

$events = json_decode($content, TRUE);
foreach($events['data'] as $key=>$val){
$name = $val['name'];//whatever else you want to get out
}
?>

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here