反应性扩展框架(Reactive Extensions)【WP7学习札记之十六】
Reactive 编程模型:Reactive Extensions for .NET Framework 是一个托管库,它提供用于编写反应式应用程序的 API。反应式应用程序是由其环境驱动的。在反应式模型中,数据流、异步请求以及事件都表示为可观察序列。应用程序可以订阅这些可观察序列,以在新数据到达时接收异步消息。Reactive Extensions 允许应用程序使用查询运算符组合这些序列。如果您的应用程序与多个数据源(如用户输入事件、Web 服务请求以及系统通知)交互,则管理所有这些交互的便利方法是为每个数据流实现单独的处理程序。在这些处理程序中,您必须提供代码以在所有不同的数据流之间进行协调并将该数据处理为可使用的形式。Reactive Extensions 允许您编写一个查询,该查询将所有这些数据流组合成触发单个处理程序的单个流。筛选、同步和转换数据等工作由 Reactive Extensions 查询执行,以便您的处理程序只需对接收的数据进行反应并对该数据进行某些处理。
下面是林永坚对反应性扩展框架的总结:
在前一篇博文中,学习了如何使用地理位置服务(Location Service),但是在模拟器上我们无法获得相关的地理位置信息,下面用这节学习的内容模拟地理位置的数据~
MainPage.xaml代码如下:
<phone:PhoneApplicationPage
x:Class="RecativeExtension.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" d:DesignWidth="480" d:DesignHeight="768"
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 x:Name="ApplicationTitle" Text="示例程序" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock x:Name="PageTitle" Text="反应性扩展框架" 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">
<TextBlock Height="30" HorizontalAlignment="Left" Margin="27,76,0,0" Name="textBlock1" Text="经度" VerticalAlignment="Top" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="24,153,0,0" Name="textBlock2" Text="纬度" VerticalAlignment="Top" />
<TextBox Height="72" HorizontalAlignment="Left" Margin="70,50,0,0" Name="logTxtBox" Text="" VerticalAlignment="Top" Width="460" />
<TextBox Height="72" HorizontalAlignment="Left" Margin="70,128,0,0" Name="latTxtBox" Text="" VerticalAlignment="Top" Width="460" />
<Button Content="开始" Height="72" HorizontalAlignment="Left" Margin="70,299,0,0" Name="button1" VerticalAlignment="Top" Width="160" Click="button1_Click" />
<Button Content="结束" Height="72" HorizontalAlignment="Left" Margin="248,299,0,0" Name="button2" VerticalAlignment="Top" Width="160" Click="button2_Click" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="24,230,0,0" Name="textBlock3" Text="状态" VerticalAlignment="Top" />
<TextBox Height="72" HorizontalAlignment="Left" Margin="70,206,0,0" Name="statusTxtBox" Text="" VerticalAlignment="Top" Width="460" />
</Grid>
</Grid>
<!--Sample code showing usage of ApplicationBar-->
<!--<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
<shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
<shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
<shell:ApplicationBar.MenuItems>
<shell:ApplicationBarMenuItem Text="MenuItem 1"/>
<shell:ApplicationBarMenuItem Text="MenuItem 2"/>
</shell:ApplicationBar.MenuItems>
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>-->
</phone:PhoneApplicationPage>
MainPage.xaml.cs代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Device.Location;
using Microsoft.Phone.Reactive;
using System.Threading;
namespace RecativeExtension
{
public partial class MainPage : PhoneApplicationPage
{
GeoCoordinateWatcher watcher;
bool useEmulation = true;
// Constructor
public MainPage()
{
InitializeComponent();
}
private void StartEmulation()
{
// EmulatePositionChangedEvents returns an IEnumerable object.
// Convert this to an Observable sequence.
var position = EmulatePositionChangedEvents().ToObservable();
// Subscribe to the Observable sequence.
// Use null for the sender parameter to the event handler.
position.Subscribe(evt => watcher_PositionChanged(null, evt));
}
static IEnumerable<GeoPositionChangedEventArgs<GeoCoordinate>> EmulatePositionChangedEvents()
{
// Create a Random object to create random numbers.
Random random = new Random();
// Loop infinitely.
for (; ; )
{
// Pause for 100 milliseconds in each loop.
Thread.Sleep(random.Next(100));
// Generate a random latitude and longitude. You could also load position data from a file or
// generate the data from user input.
double latitude = (random.NextDouble() * 180.0) - 90.0; // latitude is between -90 and 90
double longitude = (random.NextDouble() * 360.0) - 180.0; // longitude is between -180 and 180
// Use yield to return a new instance of the GeoPositionChangedEventArgs class that is exposed
// through the IEnumerable interface.
yield return new GeoPositionChangedEventArgs<GeoCoordinate>(
new GeoPosition<GeoCoordinate>(DateTimeOffset.Now, new GeoCoordinate(latitude, longitude)));
}
}
// Event handler for location data. This invokes code on the
// page's main thread.
private void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
Dispatcher.BeginInvoke(() =>
{
latTxtBox.Text = e.Position.Location.Latitude.ToString("0.00");
logTxtBox.Text = e.Position.Location.Longitude.ToString("0.00");
});
}
private void button1_Click(object sender, RoutedEventArgs e)
{
// First, handle the case where emulation is not being used.
if (!useEmulation)
{
// Initialize the GeoCoordinateWatcher.
watcher = new GeoCoordinateWatcher();
// Reactive Extensions uses Observable sequences to represent data streams.
// Create an Observable sequence from the event stream using the FromEvent method.
// This method uses the .NET Generic syntax to specify the type of event args for the event.
// The parameters to the method are the add and remove handlers of the GeoCoordinateWatcher object.
IObservable<IEvent<GeoPositionChangedEventArgs<GeoCoordinate>>> positionEventAsObservable =
Observable.FromEvent<GeoPositionChangedEventArgs<GeoCoordinate>>(
ev => watcher.PositionChanged += ev,
ev => watcher.PositionChanged -= ev);
// Subscribe to the observable data stream. You can use the same event handler as if you
// were using the GeoCoordinateWatcher.PositionChanged event directly.
var positionSubscription = positionEventAsObservable.Subscribe(
args => watcher_PositionChanged(args.Sender, args.EventArgs));
// Start the GeoCoordinateWatcher to begin receiving data from the Location Service.
watcher.Start();
}
else
{
// Start the thread on which emulated location data is generated.
// The method StartEmulation is defined next.
Thread emulationThread = new Thread(StartEmulation);
emulationThread.Start();
}
}
private void button2_Click(object sender, RoutedEventArgs e)
{
}
}
}
程序运行效果如下:
关于反应性扩展框架,请参见MSDN:Windows Phone 的 Reactive Extensions for .NET Framework 概述 、How to: Use Reactive Extensions to Emulate and Filter Location Data for Windows Phone
另外,《Windows Phone 7高级编程》P160、P168也提供了2中模拟位置的方法,个人以为比反应性扩展框架模拟的简单,感兴趣的博友可以自己学习一下~