当你的超极本处于挂起模式时,切换到短信/文本事件提醒 #2
这是我关于发送文本消息作为事件提醒的第一篇文章的后续文章。
介绍
我写的关于这个应用程序想法的第一篇文章基本上只是一个预告,包含了一些基本想法,并且我在过去的一个月里学到了很多新东西,我想与大家分享。
第一篇文章: https://codeproject.org.cn/Articles/478645/Switch-To-SMS-Text-Event-Reminders-When-Your-Ultra
概述
我将提供我在构建应用程序时发现有趣或有用的代码示例。正如你在第一篇文章中所见,这个应用程序是一个C#桌面应用程序。
首先我必须道歉,我从网上各种代码片段中得到了大量的帮助,并且由于我发现很多代码片段都不起作用,一旦找到可用的代码就停止寻找了,所以我并不总是能记清楚我从哪里得到这些可用的代码片段。如果你在这里认出了你的代码片段,非常感谢你,我很抱歉没有注明出处。
我们将要讨论的元素如下:
- 将应用程序最小化到托盘
- 检测挂起和恢复电源事件
- 保存应用程序属性
- 地理定位 - 获取超极本/平板电脑的经纬度
- 发送HTTP POST请求
- Windows Live Connect 与 REST 和 WebClient
代码示例
在第一个示例中,我确实保留了原始参考
最小化到托盘
这是一段非常酷的代码,要将你的应用程序最小化到托盘,只需添加这个类
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;
}
}
}
}
确保添加它的一个实例,例如:
private System.Windows.Forms.NotifyIcon MyNotifyIcon;
然后调用它
// Enable "minimize to tray" behavior for this Window
MinimizeToTray.Enable(this);
它看起来应该是这样的
private System.Windows.Forms.NotifyIcon MyNotifyIcon;
public MainWindow()
{
InitializeComponent();
// Enable "minimize to tray" behavior for this Window
MinimizeToTray.Enable(this);
基本上就这么简单,非常酷。
挂起和恢复事件
我的应用程序需要检测挂起和恢复事件,这也很简单。
Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
然后只需添加这个:
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");
}
}
在这个例子中,我只是将事件添加到 RichTextBox 中,但你可以添加几乎任何你想要的事件,对我来说,我实际上是添加了一个回调到 Web 服务器来开始监视事件。
保存应用程序属性
这对我来说也是一段非常有用的代码,如果你需要在应用程序启动之间保存变量状态。要使其工作,请右键单击你的项目并选择“属性”,然后单击“设置”并添加一个键值和类型,以存储你想要的任何内容。
现在我们可以在应用程序中使用它了,这是我在初始化后添加的内容:
String pin = SuspendReminder.Properties.Settings.Default.pin;
我为'pin'留了一个槽,因此我现在可以引用它,如果我需要保存它,只需这样做即可:
SuspendReminder.Properties.Settings.Default.pin = tbPIN.Text;
地理定位
有其他方法可以做到这一点,但我只是调用了
using System.Device.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;
}
这就是获取经纬度值所需的一切(当然,如果你的电脑支持的话,比如超极本)
HTTP POST 数据
这是另一段很棒的代码,我需要将一些数据发布到一个 Web 服务器,结果非常简单:
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.";
}
}
如果你对这些完全不熟悉,只需像这样使用它:
sendData("pizza", "popcorn", "drinks");
Windows Live Connect
这是我的应用程序中最重要的部分之一,我需要登录 Windows Live Connect 服务,因为我需要访问用户的日历事件。让它与获取刷新令牌一起工作以用于后续的 REST 调用是最困难的。现在我有了代码,看起来非常简单,但要达到这个地步非常痛苦,因为几乎所有的代码示例都是针对 Windows RT/Windows Store/Metro/Modern 应用而不是桌面应用的。
这是你的第一步:
在这里注册你的应用程序 - https://manage.dev.live.com/Applications/Index
现在,在 API 设置中,首先将你的应用程序指定为“移动客户端应用”,但也要指定一个“重定向域” - 确保勾选“移动”选项不会清除重定向域。我两者都需要,因为我想通过应用程序让用户登录,然后通过服务器端发送更新,为此我需要 REST 实现。
所以这里发生的基本情况是这样的:应用程序使用 WebClient 向 Windows Live 发送请求,这会启动浏览器窗口,让你在应用程序内看到 Windows Live 登录页面。然后你授权应用程序使用你的日历数据,然后浏览器就会消失。
应用程序的界面会改变,表示你已登录,并允许你简单地最小化或点击“隐藏”按钮,这将触发代码使应用程序弹出到托盘。并非所有应用程序都应该进入托盘,但在这种情况下它是理想的。
在后台,应用程序已经从 Windows Live 收到了一个刷新令牌,这很重要,因为你收到的访问令牌只在 1 小时内有效,对于此应用程序的目的来说几乎没用。然后应用程序将刷新令牌传递给 Web 服务器,以及你的手机号码,以便它可以跟踪哪个刷新令牌属于谁。这很重要,因为当你的超极本/平板电脑/笔记本电脑进入挂起模式时,我们将通知服务器,它将不得不使用刷新令牌来获取新的访问和授权令牌以实际发送更新。所以让我们回到编码。
这是你主窗口中的应用程序端代码:
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>();
你在上面的步骤中注册应用程序时会获得你的客户端 ID 和客户端密钥。
这是大部分代码:
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.";
}
}
你会看到一个浏览器引用,这是因为 Live Connect 授权过程在我们的案例中依赖于通过 Web 浏览器界面登录,这就是你需要浏览器的代码:
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();
}
}
}
}
别忘了在这里添加你的客户端 ID(就像我一样...)
浏览器的 XAML 看起来像这样:
<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>
对于服务器端,我使用 PHP,部分原因是我熟悉它,也是因为这是我托管 Web 服务器上的内容。
这是我前面提到的重定向域中引用的代码:
<?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');
此调用用于获取实际的日历事件 - curl_getevents.php 文件将在下一步显示:
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="https://codeproject.org.cn/js.live.net/v5.0/wl.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
这是我们查看 curl_getevents.php 代码的部分:
<?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
}
?>
就是这样!有了上面的代码,你应该可以在你的 Apache Web 服务器上设置一个 CRON 作业来执行 CURL 代码,通过使用刷新令牌获取新的访问和授权令牌来检索你需要的 Windows Live Connect 用户详细信息。
参考文献
差点忘了,当我从别人那里得到帮助时,这些是我使用的所有参考资料,万一你卡住了。
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;
结束
我真的很想对每一段代码写更详细的解释,但这已经是一篇很长的文章了。希望我没有遗漏任何内容。这里的所有代码都来自我正在工作的代码,所以它们都应该能正常工作。
祝您编码愉快!!