Одной из особенностей, которые меня взволновали в Windows Phone 8 SDK, являются API-интерфейсы Proximity. Эти API предоставляют вашему приложению доступ к Bluetooth-соединению и Wi-Fi Direct / NFC для совместного использования всего, что вы хотите, с 2-х совместимых телефонов, на которых запущено приложение, ожидающее соединения. Мой пример довольно прост и не подходит для реальной жизни, но это хорошо для начинающих.
В этом примере мы будем интенсивно использовать класс PeerFinder, который предоставляет функции для принятия и ожидания подключений и асинхронного поиска одноранговых узлов для подключения. Как только соединение установлено, мы можем читать и писать, используя двоичные устройства чтения через сокеты. Довольно легко!
MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Windows.Foundation;
using Windows.Networking.Proximity;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
namespace PhoneApp1
{
public partial class MainPage : PhoneApplicationPage
{
private IReadOnlyList<PeerInformation> _peerInformationList;
private PeerInformation _requestingPeer;
private StreamSocket _socket = null;
private bool _socketClosed = true;
private DataWriter _dataWriter;
private DataReader _dataReader;
private bool _triggeredConnectSupported = false;
bool _isLaunchedByTap = false;
private bool _browseConnectSupported = false;
// Constructor
public MainPage()
{
InitializeComponent();
Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
_triggeredConnectSupported = (PeerFinder.SupportedDiscoveryTypes & PeerDiscoveryTypes.Triggered) ==
PeerDiscoveryTypes.Triggered;
_browseConnectSupported = (PeerFinder.SupportedDiscoveryTypes & PeerDiscoveryTypes.Browse) ==
PeerDiscoveryTypes.Browse;
if (_triggeredConnectSupported || _browseConnectSupported)
{
// This scenario demonstrates "PeerFinder" to browse for peers to connect to using a StreamSocket
PeerFinder_StartFindingPeersButton.Click += new RoutedEventHandler(PeerFinder_StartFindingPeers);
PeerFinder_BrowsePeersButton.Click += new RoutedEventHandler(PeerFinder_BrowsePeers);
PeerFinder_ConnectButton.Click += new RoutedEventHandler(PeerFinder_Connect);
PeerFinder_AcceptButton.Click += new RoutedEventHandler(PeerFinder_Accept);
PeerFinder_SendButton.Click += new RoutedEventHandler(PeerFinder_Send);
PeerFinder_StartFindingPeersButton.Visibility = Visibility.Visible;
}
}
string[] rgConnectState = {"PeerFound",
"Listening",
"Connecting",
"Completed",
"Canceled",
"Failed"};
public void NotifyUser(string strMessage, NotifyType type)
{
Dispatcher.BeginInvoke( () =>
{
switch (type)
{
// Use the status message style.
case NotifyType.StatusMessage:
MessageBox.Show(strMessage,"Status",MessageBoxButton.OK);
break;
// Use the error message style.
case NotifyType.ErrorMessage:
MessageBox.Show(strMessage,"Error",MessageBoxButton.OK);
break;
}
});
}
private void TriggeredConnectionStateChangedEventHandler(object sender, TriggeredConnectionStateChangedEventArgs eventArgs)
{
if (eventArgs.State == TriggeredConnectState.PeerFound)
{
// Use this state to indicate to users that the tap is complete and
// they can pull there devices away.
NotifyUser("Tap complete, socket connection starting!", NotifyType.StatusMessage);
}
if (eventArgs.State == TriggeredConnectState.Completed)
{
NotifyUser("Socket connect success!", NotifyType.StatusMessage);
// Grab the socket that just connected.
_socket = eventArgs.Socket;
Dispatcher.BeginInvoke(() =>
{
this.PeerFinder_StartSendReceive();
});
}
if (eventArgs.State == TriggeredConnectState.Failed)
{
NotifyUser("Socket connect failed!", NotifyType.ErrorMessage);
}
}
private bool _peerFinderStarted = false;
private void SocketError(String errMessage)
{
NotifyUser(errMessage, NotifyType.ErrorMessage);
PeerFinder_StartFindingPeersButton.Visibility = Visibility.Visible;
if (_browseConnectSupported)
{
PeerFinder_BrowsePeersButton.Visibility = Visibility.Visible;
}
PeerFinder_SendButton.Visibility = Visibility.Collapsed;
PeerFinder_MessageBox.Visibility = Visibility.Collapsed;
if (!_socketClosed)
{
_socketClosed = true;
_socket.Dispose();
_socket = null;
}
}
async private void PeerFinder_Send(object sender, RoutedEventArgs e)
{
NotifyUser("", NotifyType.ErrorMessage);
String message = PeerFinder_MessageBox.Text;
PeerFinder_MessageBox.Text = ""; // clear the input now that the message is being sent.
if (!_socketClosed)
{
if (message.Length > 0)
{
try
{
uint strLength = _dataWriter.MeasureString(message);
_dataWriter.WriteUInt32(strLength);
_dataWriter.WriteString(message);
uint numBytesWritten = await _dataWriter.StoreAsync();
if (numBytesWritten > 0)
{
NotifyUser("Sent message: " + message + ", number of bytes written: " + numBytesWritten, NotifyType.StatusMessage);
}
else
{
SocketError("The remote side closed the socket");
}
}
catch (Exception err)
{
if (!_socketClosed)
{
SocketError("Failed to send message with error: " + err.Message);
}
}
}
else
{
NotifyUser("Please type a message", NotifyType.ErrorMessage);
}
}
else
{
SocketError("The remote side closed the socket");
}
}
async private void PeerFinder_Accept(object sender, RoutedEventArgs e)
{
NotifyUser("Connecting to " + _requestingPeer.DisplayName + "....", NotifyType.StatusMessage);
PeerFinder_AcceptButton.Visibility = Visibility.Collapsed;
try
{
_socket = await PeerFinder.ConnectAsync(_requestingPeer);
NotifyUser("Connection succeeded", NotifyType.StatusMessage);
PeerFinder_StartSendReceive();
}
catch (Exception err)
{
NotifyUser("Connection to " + _requestingPeer.DisplayName + " failed: " + err.Message, NotifyType.ErrorMessage);
}
}
private void PeerConnectionRequested(object sender, ConnectionRequestedEventArgs args)
{
_requestingPeer = args.PeerInformation;
Dispatcher.BeginInvoke( () =>
{
NotifyUser("Connection requested from peer " + args.PeerInformation.DisplayName, NotifyType.StatusMessage);
this.PeerFinder_AcceptButton.Visibility = Visibility.Visible;
this.PeerFinder_SendButton.Visibility = Visibility.Collapsed;
this.PeerFinder_MessageBox.Visibility = Visibility.Collapsed;
});
}
async void PeerFinder_StartReader()
{
try
{
uint bytesRead = await _dataReader.LoadAsync(sizeof(uint));
if (bytesRead > 0)
{
uint strLength = (uint)_dataReader.ReadUInt32();
bytesRead = await _dataReader.LoadAsync(strLength);
if (bytesRead > 0)
{
String message = _dataReader.ReadString(strLength);
NotifyUser("Got message: " + message, NotifyType.StatusMessage);
PeerFinder_StartReader(); // Start another reader
}
else
{
SocketError("The remote side closed the socket");
}
}
else
{
SocketError("The remote side closed the socket");
}
}
catch (Exception e)
{
if (!_socketClosed)
{
SocketError("Reading from socket failed: " + e.Message);
}
}
}
// Start the send receive operations
void PeerFinder_StartSendReceive()
{
PeerFinder_SendButton.Visibility = Visibility.Visible;
PeerFinder_MessageBox.Visibility = Visibility.Visible;
// Hide the controls related to setting up a connection
PeerFinder_ConnectButton.Visibility = Visibility.Collapsed;
PeerFinder_AcceptButton.Visibility = Visibility.Collapsed;
PeerFinder_FoundPeersList.Visibility = Visibility.Collapsed;
PeerFinder_BrowsePeersButton.Visibility = Visibility.Collapsed;
PeerFinder_StartFindingPeersButton.Visibility = Visibility.Collapsed;
_dataReader = new DataReader(_socket.InputStream);
_dataWriter = new DataWriter(_socket.OutputStream);
_socketClosed = false;
PeerFinder_StartReader();
}
async void PeerFinder_Connect(object sender, RoutedEventArgs e)
{
NotifyUser("", NotifyType.ErrorMessage);
PeerInformation peerToConnect = null;
try
{
// If nothing is selected, select the first peer
if (PeerFinder_FoundPeersList.SelectedIndex == -1)
{
peerToConnect = _peerInformationList[0];
}
else
{
peerToConnect = _peerInformationList[PeerFinder_FoundPeersList.SelectedIndex];
}
NotifyUser("Connecting to " + peerToConnect.DisplayName + "....", NotifyType.StatusMessage);
_socket = await PeerFinder.ConnectAsync(peerToConnect);
NotifyUser("Connection succeeded", NotifyType.StatusMessage);
PeerFinder_StartSendReceive();
}
catch (Exception err)
{
NotifyUser("Connection to " + peerToConnect.DisplayName + " failed: " + err.Message, NotifyType.ErrorMessage);
}
}
async void PeerFinder_BrowsePeers(object sender, RoutedEventArgs e)
{
NotifyUser("Finding Peers...", NotifyType.StatusMessage);
try
{
_peerInformationList = await PeerFinder.FindAllPeersAsync();
}
catch (Exception ex)
{
Debug.WriteLine("FindAll throws exception" + ex.Message);
}
Debug.WriteLine("Async operation completed");
NotifyUser("No peers found", NotifyType.StatusMessage);
try{
if (_peerInformationList.Count > 0)
{
PeerFinder_FoundPeersList.Items.Clear();
for (int i = 0; i < _peerInformationList.Count; i++)
{
ListBoxItem item = new ListBoxItem();
item.Content = _peerInformationList[i].DisplayName;
PeerFinder_FoundPeersList.Items.Add(item);
}
PeerFinder_ConnectButton.Visibility = Visibility.Visible;
PeerFinder_FoundPeersList.Visibility = Visibility.Visible;
NotifyUser("Finding Peers Done", NotifyType.StatusMessage);
}}
catch
{
NotifyUser("No peers found", NotifyType.StatusMessage);
PeerFinder_ConnectButton.Visibility = Visibility.Collapsed;
PeerFinder_FoundPeersList.Visibility = Visibility.Collapsed;
}
}
void PeerFinder_StartFindingPeers(object sender, RoutedEventArgs e)
{
NotifyUser("", NotifyType.ErrorMessage);
if (!_peerFinderStarted)
{
// attach the callback handler (there can only be one PeerConnectProgress handler).
PeerFinder.TriggeredConnectionStateChanged += new TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs>(TriggeredConnectionStateChangedEventHandler);
// attach the incoming connection request event handler
PeerFinder.ConnectionRequested += new TypedEventHandler<object, ConnectionRequestedEventArgs>(PeerConnectionRequested);
// start listening for proximate peers
PeerFinder.Start();
_peerFinderStarted = true;
if (_browseConnectSupported && _triggeredConnectSupported)
{
NotifyUser("Tap another device to connect to a peer or click Browse for Peers button.", NotifyType.StatusMessage);
PeerFinder_BrowsePeersButton.Visibility = Visibility.Visible;
}
else if (_triggeredConnectSupported)
{
NotifyUser("Tap another device to connect to a peer.", NotifyType.StatusMessage);
}
else if (_browseConnectSupported)
{
NotifyUser("Click Browse for Peers button.", NotifyType.StatusMessage);
}
}
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached. The Parameter
/// property is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (_triggeredConnectSupported || _browseConnectSupported)
{
// Initially only the advertise button should be visible.
PeerFinder_StartFindingPeersButton.Visibility = Visibility.Visible;
PeerFinder_BrowsePeersButton.Visibility = Visibility.Collapsed;
PeerFinder_ConnectButton.Visibility = Visibility.Collapsed;
PeerFinder_FoundPeersList.Visibility = Visibility.Collapsed;
PeerFinder_SendButton.Visibility = Visibility.Collapsed;
PeerFinder_AcceptButton.Visibility = Visibility.Collapsed;
PeerFinder_MessageBox.Visibility = Visibility.Collapsed;
PeerFinder_MessageBox.Text = "Hello World";
if (IsLaunchedByTap())
{
NotifyUser("Launched by tap", NotifyType.StatusMessage);
PeerFinder_StartFindingPeers(null, null);
}
else
{
if (!_triggeredConnectSupported)
{
NotifyUser("Tap based discovery of peers not supported", NotifyType.ErrorMessage);
}
else if (!_browseConnectSupported)
{
NotifyUser("Browsing for peers not supported", NotifyType.ErrorMessage);
}
}
}
else
{
NotifyUser("Tap based discovery of peers not supported \nBrowsing for peers not supported", NotifyType.ErrorMessage);
}
}
// Invoked when the main page navigates to a different scenario
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
if (_peerFinderStarted)
{
// detach the callback handler (there can only be one PeerConnectProgress handler).
PeerFinder.TriggeredConnectionStateChanged -= new TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs>(TriggeredConnectionStateChangedEventHandler);
// detach the incoming connection request event handler
PeerFinder.ConnectionRequested -= new TypedEventHandler<object, ConnectionRequestedEventArgs>(PeerConnectionRequested);
PeerFinder.Stop();
if (_socket != null)
{
_socketClosed = true;
_socket.Dispose();
_socket = null;
}
_peerFinderStarted = false;
}
}
public bool IsLaunchedByTap()
{
bool isLaunchedByTap = _isLaunchedByTap;
_isLaunchedByTap = false;
return isLaunchedByTap;
}
public enum NotifyType
{
StatusMessage,
ErrorMessage
};
}
}
MainPage.xaml
<phone:PhoneApplicationPage
x:Class="PhoneApp1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="{Binding Path=LocalizedResources.ApplicationTitle, Source={StaticResource LocalizedStrings}}" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="{Binding Path=LocalizedResources.PageTitle, Source={StaticResource LocalizedStrings}}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical" Margin="0,10,0,0" Grid.Row="0">
<Button x:Name="PeerFinder_StartFindingPeersButton" Content="Advertise" Visibility="Collapsed" Margin="0,0,10,0"/>
<Button x:Name="PeerFinder_BrowsePeersButton" Content="Browse for Peers" Visibility="Collapsed" Margin="0,0,10,0"/>
<Button x:Name="PeerFinder_ConnectButton" Content="Connect To a Peer" Visibility="Collapsed" Margin="0,0,10,0"/>
<ListBox x:Name="PeerFinder_FoundPeersList" Visibility="Collapsed">
</ListBox>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="0,10,0,0" Grid.Row="1">
<Button x:Name="PeerFinder_AcceptButton" Content="Accept Connection Request" Visibility="Collapsed" Margin="0,0,10,0"/>
<Button x:Name="PeerFinder_SendButton" Content="Send Message" Visibility="Collapsed" Margin="0,0,10,0"/>
<TextBox x:Name="PeerFinder_MessageBox" Visibility="Collapsed" Width="400" Margin="0,0,10,0"/>
</StackPanel>
</Grid>
<Grid x:Name="Output" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="1">
<TextBlock x:Name="PeerFinderOutputText" TextWrapping="Wrap" />
</Grid>
<!--Uncomment to see an alignment grid to help ensure your controls are
aligned on common boundaries. Remove or comment out before shipping
your application.-->
<!--<Image Margin="0" Source="/Assets/AlignmentGrid.png" Stretch="None" IsHitTestVisible="False" />-->
</Grid>
</phone:PhoneApplicationPage>
Конечно, вам нужно немного подправить файл Proprieties (я только что проверил все авторизации). И вот у вас есть простой способ отправки сообщений между смартфоном WP8. Я не мог попробовать это, потому что виртуальная машина не может получить доступ к Bluetooth, но я уверен, что когда я получу в свои руки реальные машины, это будет.
Также я должен упомянуть, что этот код является простым портом Proximity Lab для Windows RT, и можно только надеяться, что это позволит передавать данные с Windows Phone на Windows Tablet и наоборот.