传感器 - 管理(动手实验)
Windows 7 传感器 & 定位平台
Windows 7传感器 & 定位平台可以使你的应用程序更加适应当前的环境,并且能够改变它们的外观,体验或者行为。这有一些例子:
• 阳光明媚的时候,你在户外使用一个移动PC(比如,笔记本电脑或者平板电脑),那么应用程序可能会增加亮度,对比度来增加屏幕的易读性。
• 应用程序可能会提供一些特定的信息,比如附近的餐馆。
• 应用程序可能会像游戏控制器一样使用3D加速计和按钮。
• 应用程序也可能会使用人体传感器来改变Messenger的登录状态。
图例 1
可调MSDN阅读器,使用了环境光线感应器来调整对比度,尺寸和颜色饱和度
传感器&定位平台,对所有的解决方案都有很多优点:
• 硬件-独立:不再需要为一个独立的提供商的API进行学习和投资;所有的传感器类型控制起来都非常相似。
• 隐私:Microsoft意识到,传感器和本地数据都是私人的,个人的验证信息,所有的传感器在默认情况下都是关闭的。你可以通过控制面板随时打开/关闭传感器。应用程序将会提示你一个安全的允许的UI用户界面,来打开指定的传感器。
• 应用程序共享:多个应用程序可以同时使用同一个传感器的数据。
• 定位简单化:定位API能够使你不需要关心获取信息的特殊结构,就能得到所需要的位置。比如。GPS,发射塔或者WIFI热点。定位API将自动选择可用的最正确的传感器数据。另外,你也不需要去实现类似于NMEA的GPS协议了。
目标
在本次动手实验中,你将了解到如何将传感器的API整合到你的WPF应用程序中。与定位API相同,传感器API也是基于COM的。所以你可以用类型安全和方便的方式使用Windows API。
系统需求
若完成此实验,你必须需要以下组件:
• Microsoft Visual Studio 2008 SP1
• Windows 7 RC或更新的版本
• 完全支持DirectX 和 PixelShaders的显示卡。
• 安装了驱动的Windows 7兼容环境光线传感器硬件,或者虚拟环境光线传感器
• 安装了驱动的Windows 7兼容的3D加速计硬件(可选)
练习: 在WPF 应用程序中整合传感器API
任务 1 – 准备一个WPF 项目,为整合传感器做准备
1. 启动Visual Studio 2008,打开包含WPF应用程序的初始解决方案。这个解决方案在你的Starter文件夹中。
图例 2
启动解决方案
WPF项目SensorHOL,已经捆绑了Grayscale PixelShader效果,这个效果我们将在后面的实验中使用到,并且这个项目也包含了很多在将来的WPF控件中需要使用的资源。
2. 向项目添加一个名叫SensorViewModel的新的类。
MainWindow视图将会对它的属性进行数据绑定。通过使用这个模式,视图将监控它的更新,并且用ViewModel被动等待来进行替换。
图例 3
创建 SensorViewModel 类
3. 在SensorViewModel类中实现InotifyPropertyChanged接口。
C#
class SensorViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
4. 添加如下的代码(当我们需要消息通知的时候,我们将调用这个方法):
C#
#region INotifyPropertyChanged Members
private void NotifyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, newPropertyChangedEventArgs(propertyName));
}
}
5. 为了使ViewModel连接到视图,在MainWindow.xaml.cs 中的InitializeComponent()后面添加下面的代码:
C#
public MainWindow()
{
InitializeComponent();
DataContext = new SensorViewModel();
}
测试你的代码以保证它是能够编译的。
任务 2 –向WPF 应用程序中添加排版
图例 4
Sensor HOL 基本排版
在这个任务中,你将设计MainWindow的排版。它将包含3个部分:
• 顶部:“Browse…”按钮和照片名称
• 左边:传感器的指示器
• 右边:图片
一个滑块将图片分隔开;图片的每个部分,将根据传感器数据独立的展现出当前的光线强度。
1. 打开 MainWindow.xaml 文件。
在WPF中,Grid是一个强大的排版布局。它将是我们在本次实验中进行排版设计的主要方式。
2. 我们从添加行和列的定义开始。
XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="65"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
3. 接着,紧接着列的定义代码,向Grid中添加屏幕的右边部分。这个Grid将包含图片和滑块。
XAML
<Grid Grid.Column="1" Grid.Row="1" x:Name="OverlayContent"
HorizontalAlignment="Center" VerticalAlignment="Center">
</Grid>
4. 在“OverlayContent” Grid中,添加两个图片控件。每一个都将呈现出不同的效果,并且他们都是重叠的。
XAML
<Image Stretch="Uniform" Margin="10">
</Image>
<Image Stretch="Uniform" Margin="10">
</Image>
5. 接着,在Grid中添加Slider控件。注意滑块的高度和宽度都是与grid的样式进行数据绑定的。
XAML
<Slider x:Name="OverlayAdjustmentSlider"
Width="{Binding ElementName=OverlayContent, Path=ActualWidth}"
Height="{Binding ElementName=OverlayContent, Path=ActualHeight}"
FocusVisualStyle="{x:Null}" Maximum="1" Minimum="0" Value="0.5"
Template="{DynamicResource Slider_ControlTemplate}">
</Slider>
6. 让我们关注布局左边的区域:仅在“OverlayContent” Grid之后,添加另外一个Grid
XAML
<Grid Width="150" Height="240" Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top">
</Grid>
在这个Grid中,我们需要添加一个传感器指示控件和其余的传感器指示器。内部的Grid的标题叫“Sensor Data”。
7. 创建一个有标题的边框。
XAML
<Border Background="{DynamicResource Brush_PopupBackground}">
<Border.Effect>
<DropShadowEffect BlurRadius="12" Direction="-90" ShadowDepth="3"/>
</Border.Effect>
</Border>
<Border Background="{DynamicResource Brush_PopupBackground}" BorderBrush="{StaticResource Brush_PopupBorder}" BorderThickness="0,1,0,1" CornerRadius="3">
<Grid TextElement.FontFamily="Segoe UI" TextElement.FontSize="10">
<TextBlock HorizontalAlignment="Center" Margin="0,10" VerticalAlignment="Top" FontFamily="Segoe UI" FontSize="13" Foreground="White" Text="Sensor Data"/>
</Grid>
</Border>
8. 最后的部分由一个独立包含按钮和标题的Grid组成。
XAML
<Grid Grid.ColumnSpan="2" HorizontalAlignment="Stretch">
<Button HorizontalAlignment="Left" Width="70" Margin="5" Height="40" Content="Browse..." Click="Button_Click"/>
<TextBlock Foreground="White" Grid.Column="1" Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Segoe UI"/>
</Grid>
9. 现在,向MainWindow视图中添加“Browse…”按钮的点击事件代码。
C#
private void Button_Click(object sender, RoutedEventArgs e)
{
}
你的MainWindow 的XAML看起来应该像下面这样:
XAML
<Window x:Class="SensorHOL.MainWindow"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" WindowState="Maximized" Background="Black"> <Grid>
<Grid.RowDefinitions>
<RowDefinition Height="65"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="1" Grid.Row="1" x:Name="OverlayContent"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Stretch="Uniform" Margin="10">
</Image>
<Image Stretch="Uniform" Margin="10">
</Image>
<Slider x:Name="OverlayAdjustmentSlider" Width="{Binding
ElementName=OverlayContent, Path=ActualWidth}"
Height="{Binding ElementName=OverlayContent,
Path=ActualHeight}" FocusVisualStyle="{x:Null}"
Maximum="1" Minimum="0" Value="0.5"
Template="{DynamicResource Slider_ControlTemplate}">
</Slider>
</Grid>
<Grid Width="150" Height="240" Grid.Row="1"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Border Background="{DynamicResource
Brush_PopupBackground}">
<Border.Effect>
<DropShadowEffect BlurRadius="12" Direction="-90"
ShadowDepth="3"/>
</Border.Effect>
</Border>
<Border Background="{DynamicResource
Brush_PopupBackground}" BorderBrush="{StaticResource
Brush_PopupBorder}" BorderThickness="0,1,0,1"
CornerRadius="3">
<Grid TextElement.FontFamily="Segoe UI"
TextElement.FontSize="10">
<TextBlock HorizontalAlignment="Center" Margin="0,10" VerticalAlignment="Top"
FontFamily="Segoe UI" FontSize="13"
Foreground="White" Text="Sensor Data"/>
</Grid>
</Border>
</Grid>
<Grid Grid.ColumnSpan="2" HorizontalAlignment="Stretch">
<Button HorizontalAlignment="Left" Width="70" Margin="5"
Height="40" Content="Browse..." Click="Button_Click"/>
<TextBlock Foreground="White" Grid.Column="1" Grid.Row="2"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontFamily="Segoe UI" />
</Grid>
</Grid>
</Window>
10. 测试你的代码以保证它能够编译。
任务 3 –添加图片指定逻辑
在前面的任务中,我们已经为应用程序设计了布局,现在,是向里面填充内容的时候了。
1. 打开 SensorViewModel 。在你添加了一个新的属性后,用户就可以浏览一个文件,并且这个属性需要包含它的图片的选择范围。
2. 添加 NotifyChange 方法。
每次这个属性发生了变化,这个视图将负责它的变化。
C#
private string _imagePath;
public string ImagePath
{
get { return _imagePath; }
set {
_imagePath = value;
NotifyChange("ImagePath");
}
}
3. 向SensorViewModel中添加System.IO命名空间。
C#
using System.IO;
4. 添加一个只读属性,用来从ImagePath属性中请求文件名称。
C#
public string ImageName
{
get
{
return Path.GetFileNameWithoutExtension(ImagePath);
}
}
现在,我们需要来确认当ImagePath发生变化时,UI用户界面能够与ImageName的变化很好的进行绑定。
5. 添加一个NotifyChange的调用,这次是使用ImageName属性。
C#
private string _imagePath;
public string ImagePath
{
get { return _imagePath; }
set {
_imagePath = value;
NotifyChange("ImagePath");
NotifyChange("ImageName");
}
}
6. 现在让我们来实现:返回视图,并且注册必要的绑定。
XAML
<Image Source="{Binding ImagePath}" Stretch="Uniform" Margin="10">>
</Image>
<Image Source="{Binding ImagePath}" Stretch="Uniform" Margin="10">>
</Image>
7. 现在你可以对文件名称标签进行数据绑定(注意:绑定方式是one-way,因为这个属性是只读的)。
XAML
<TextBlock Foreground="White" Grid.Column="1" Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Segoe UI" Text="{Binding ImageName, Mode=OneWay}"/>
剩下的最后一部分就是为ViewModel设置图片的路径。如果要做这个操作,你必须转到后台代码中,并且实现Button_Click方法。这段代码将打开一个标准的Windows文件对话框。如果用户决定选择一个图片,那么就只需要去根据他的选择去设置ViewModel的设置。
8. 首先,添加一个命名空间来使用OpenFIleDialog。
C#
using Microsoft.Win32;
9. 现在你可以实现Button_Click了。
C#
OpenFileDialog openFileDialog = new OpenFileDialog(); openFileDialog.CheckFileExists = true; openFileDialog.CheckPathExists = true;
openFileDialog.Filter = "Image Files|*.jpg|*.png"; openFileDialog.Multiselect = false;
openFileDialog.ShowReadOnly = true;
openFileDialog.ValidateNames = true; openFileDialog.RestoreDirectory = true;
openFileDialog.Title = "Find an Image";
bool? result = openFileDialog.ShowDialog();
if ((result.HasValue) && (result.Value))
{
(DataContext as SensorViewModel).ImagePath = openFileDialog.FileName;
}
10. 编译和测试你的代码:
a. 浏览一个图片;你应该能够在右边的屏幕中看到它。
b. 浏览更多的图片,并且注意UI用户界面的相关变化。
任务 4 – 建立传感器助手
1. 展开SensorHOL项目下的资源。
2. 右键点击References,然后点击Add Reference。
3. 点击Browse 选项卡,并且定位到Microsoft.WindowsAPICodePack.Sensors.dll的目录。
4. 选择该DLL然后点击OK。
5. 在新文件SensorHelper.cs中,创建一个名叫SensorHelper的新类.
6. 按照如下方式定义该类:
这个类中的SensorType类型元素,是传感器类的派生类(比如,AmbientLightSensor 或者Accelerometer3D)。
C#
using System;
using System.Collections.Generic;
using Microsoft.WindowsAPICodePack.Sensors;
using System.ComponentModel;
public class SensorHelper<SensorType, SensorPropertyType> :
INotifyPropertyChanged
where SensorType : Sensor
where SensorPropertyType : class
{
}
7. 将下面的代码粘贴到这个类中:
C#
private SensorPropertyType _value;
private SensorType _sensor;
private Guid _sensorGUID;
public SensorHelper() { }
public void Initialize()
{
SensorList<SensorType> list =
SensorManager.GetSensorsByTypeId<SensorType>();
if (list != null)
{
var permReqSensorList = new SensorList<Sensor>();
foreach (var sensor in list)
{
permReqSensorList.Add(sensor);
}
SensorManager.RequestPermission(IntPtr.Zero, true,
permReqSensorList);
foreach (var sensor in list)
{
if (HandleNewSensor(sensor) && _sensor != null)
break;
}
}
// subscribe to sensor added/removed event
SensorManager.SensorsChanged += new
SensorsChangedHandler(OnSensorsChanged);
}
Initialize()将会向传感器管理器请求类型为SensorType的传感器列表。当使用SensorHelper时,SensorType将可能是AmbientLightSensor 或者Acceleroemter3DSensor。
如果当前用户账号没有权限去使用传感器,我们将调用指定的父窗体SensorManager.RequestPermissions(),是否这个方法是同步的(模式),并且返回一个我们请求访问的传感器的列表。它将弹出如下的对话框:
在后台,CodePack API将按照COM API所期望的,把每一个传感器派生类与传感器的GUID包装为地图。这个地图信息在类中将以属性的方式来提供:
C#
[SensorDescription("97F115C8-599A-4153-8894-D2D12899918A")]
public class AmbientLightSensor : Sensor
Initialize() 将遍历所有找到的传感器,然后调用HandleNewSensor()。最后,它将订阅当新的传感器连接或者现有的传感器移除时所激发的SensorsChanged事件。
8. 将下面的代码粘贴到SensorHelper类中:
C#
private void OnSensorsChanged(SensorsChangedEventArgs change)
{
// if sensor was added
if (change.Change == SensorAvailabilityChange.Addition)
{
// we use the base Sensor class as the type parameter
// because we're not sure what sensor type was attached
Sensor sensor =
SensorManager.GetSensorBySensorId<Sensor>(change.SensorId);
// if this is the right sensro type
if (sensor is SensorType)
HandleNewSensor(sensor);
}
if (change.Change == SensorAvailabilityChange.Removal &&
_sensorGUID.Equals(change.SensorId))
{
_sensor = null;
_sensorGUID = Guid.Empty;
}
}
private bool HandleNewSensor(Sensor sensor)
{
// Sensor may become Ready in the future, register for
// StateChanged event
sensor.StateChanged += OnSensorStateChanged;
if (sensor.State == SensorState.Ready)
{
// If sensor is already in state Ready, use it
PrepareSensor(sensor);
return true;
}
return false;
}
OnSensorsChanged方法是我们在Initialize()中订阅的SensorsChanged事件的事件句柄。如果有新的传感器连接,我们将从得到的时间参数中的独立的GUID属性(SensorId)中,获取一个传感器对象基类。传感器有三个GUID:
• 实例:定义传感器的唯一标示
• 类别:例如,环境,机械,电气
• 类型:例如,温度,湿度,电压,电流,3D加速
我们接着可以检查传感器对象是否有属性类型。如果有,我们调用HandleNewSensor()。这个方法可以允许应用程序使用之后所连接的传感器。
OnSensorsChanged()同样也管理传感器的移除。它用一个变量清楚的表明哪个传感器正在使用。这个方法可以让其他传感器当其变为可用时,传感器可以被使用。
传感器还有许多其他的属性,比如Manufacturer,Model, SerialNumber等等。
这些都是标准属性。不同的传感器可能会有其他的属性。
HandleNewSensor()用来检测传感器的状态。即使传感器是连接着的,它也可能不能被使用。状态则会告诉你传感器是否已经准备就绪,如果没有就绪,原因是什么。硬件可能正在初始化,或者存在一些错误的情况,或者用户没有权限使用传感器。如果传感器已经准备就绪,我们就需要调用PrepareSensor()。我们将订阅传感器的StateChanged事件,这个事件将在传感器的状态发生改变时被触发。我们可以当传感器准备就绪并且我们没有传感器时来使用。
9. 将下面的代码粘贴到SensorHelper类中:
C#
private void OnSensorStateChanged(Sensor sender, EventArgs e)
{
if (sender.State == SensorState.Ready)
{
PrepareSensor(sender);
}
else if (!_sensorGUID.Equals(Guid.Empty) &&
_sensor.SensorId.Equals(_sensorGUID))
{
_sensor = null;
_sensorGUID = Guid.Empty;
sender.DataReportChanged -= OnDataUpdated;
}
}
private void PrepareSensor(Sensor sensor)
{
if (_sensor == null)
{
_sensorGUID = sensor.SensorId.Value;
_sensor = (SensorType)sensor;
sensor.TryUpdateData();
sensor.DataReportChanged += OnDataUpdated;
}
}
当传感器状态发生变化的时候,将会触发OnSensorStateChanged()。如果它已经准备就绪,我们将调用PrepareSensor()。如果它改变除Ready之外的状态,我们将检测它是否是我们正在使用的传感器。如果是,我们将取消订阅并且清空正在使用的传感器变量。
PrepareSensor()为使用中的传感器设置一个明显的变量,所以如果同类型的其他传感器在将来连接上,或者变为可用,也不会被覆盖。
这个方法将订阅到DataReportChanged事件。
TryUpdateData()用来强制传感器生成一个新的数据报告。一些传感器会定期的生成数据报告,其他的一些可能会在所测量的数据发生了很大的变化的时候才会生成数据报告。通过调用TryUpdateData(),我们可以立刻使用最初的测量值来初始化UI用户界面。
10. 将下面的代码粘贴到SensorHelper类中:
C#
private void OnDataUpdated(Sensor sender, EventArgs e)
{
if (sender is AmbientLightSensor)
{
_value = ((AmbientLightSensor)sender).CurrentLuminousIntensity
as SensorPropertyType;
}
else if (sender is Accelerometer3D)
{
_value = ((Accelerometer3D)sender).CurrentAcceleration as
SensorPropertyType;
}
if (_value != null)
OnPropertyChanged("Value");
}
OnDataUpdated()方法用来处理DataUpdated事件。
当传感器有新的数据报告的时候,将触发这个事件。
这些数据是通过访问一个或多个属性来获取到的(每个传感器都不相同)。想要访问一个属性,传感器需要将其置为适当的派生类。
11. 将下面的代码粘贴到SensorHelper类中:
这段代码支持将SensorHelper与UI用户界面的代码整合。
C#
public SensorPropertyType Value
{
get { return _value; }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler ph = this.PropertyChanged;
if (ph != null)
ph(this, new PropertyChangedEventArgs(name));
}
#endregion
任务 5 –将ViewModel 和传感器助手进行整合
1. 打开 SensorViewModel.cs ,并且在私有区域创建一个属性来暴露光线传感器。
C#
private SensorHelper<AmbientLightSensor, AmbientLightSensor.LuminousIntensity> _sensor;
public SensorHelper<AmbientLightSensor, AmbientLightSensor.LuminousIntensity>
LightSensor
{
get { return _sensor; }
}
2. 现在我们来对加速传感器做相同的事。
C#
private SensorHelper<Accelerometer3D, Accelerometer3D.Acceleration3D>
_acceleromaterSensor;
public SensorHelper<Accelerometer3D, Accelerometer3D.Acceleration3D>
AcceleromaterSensor
{
get { return _acceleromaterSensor; }
}
3. 我们为SensorViewModel加入一个结构体,用来设置传感器。
C#
public SensorViewModel()
{
_sensor = new SensorHelper<AmbientLightSensor,
AmbientLightSensor.LuminousIntensity>();
_sensor.Initialize();
_acceleromaterSensor = new SensorHelper<Accelerometer3D,
Accelerometer3D.Acceleration3D>();
_acceleromaterSensor.Initialize();
}
4. 编译并测试你的代码。你应该能够运行并且与传感器进行整合。
任务 6 – 在UI 用户界面添加光纤传感器的交互。
在下面的任务中,你将使用到光线亮度传感器。三个独立的元素将与其进行交互:
• 亮度指示器:一个衡量光线亮度的进度条
• 图像:当亮度增强的时候,一半图片将变成灰色。
• 图像名称:在高亮的状态下增大字号来保持可读性。
1. 添加第一个传感器指示器,转到MainWindow.xaml,并且定位到布局grid的左边(包含了一个标题叫“Sensor Data”的)。
XAML
<TextBlock HorizontalAlignment="Center" Margin="0,10" VerticalAlignment="Top" FontFamily="Segoe UI" FontSize="13" Foreground="White" Text="Sensor Data"/>
<Grid Margin="9,36,9,9">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="0.4*"/> </Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.ColumnSpan="5" Grid.RowSpan="2" Background="Black" CornerRadius="2"/>
<TextBlock Grid.Column="0" Grid.Row="2" HorizontalAlignment="Center" Margin="4,0,4,10" VerticalAlignment="Center" Foreground="White" Text="LUM"/>
<ProgressBar x:Name="LightIntensityProgressBar" Width="10" HorizontalAlignment="Center" Margin="0,8,0,5" Maximum="1" Minimum="0" Orientation="Vertical"
Style="{StaticResource SensorProgressBar}"
Value="{Binding Path=LightSensor.Value.Intensity, Mode=OneWay, Converter={StaticResource LuminosityConverter}}"/>
<Path Height="80" Grid.ColumnSpan="5" Grid.RowSpan="2" Margin="1,0" VerticalAlignment="Top" Data="M0.5,0.5L214.5,0.5 214.5,168.5 212.8,168.4C197.7,167.1 181.8,166.5 165.5,166.5 95.3,166.5 34.0,178.9 1.3,197.3L0.5,197.8z" Stretch="Fill">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="#04FFFFFF"/>
<GradientStop Offset="0.1" Color="#10FFFFFF"/>
<GradientStop Offset="1.0" Color="#18FFFFFF"/>
</LinearGradientBrush>
</Path.Fill>
</Path>
</Grid>
2. 为了我们的数据绑定工作,我们需要创建一个数据转换器,用来在传感器数据和进度条控件中进行转换。那么我们可以在MainWindow.xaml.cs文件中添加一个转换器的类。
C#
public class LuminosityConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
double d = System.Convert.ToDouble(value);
return Math.Max(Math.Min(Math.Log(d, 10000), 1.0), 0.0);
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
3. 在我们已经创建了LuminosityConverter类之后,我们需要将它声明为一个资源。返回到MainWindow.xaml,并且“local”XAML命名空间的声明要和转换器的命名空间相同。
XAML
<Window x:Class="SensorHOL.MainWindow"
xmlns=https://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=https://schemas.microsoft.com/winfx/2006/xaml
xmlns:local="clr-namespace:SensorHOL"
Title="MainWindow" WindowState="Maximized" Background="Black">
4. 在Windows控件中添加一个资源块。
XAML
<Window.Resources>
</Window.Resources>
5. 在资源块中,声明一个Converter资源。
XAML
<local:LuminosityConverter x:Key="LuminosityConverter" />
6. 编译并且运行应用程序。
你将看到灯光指示器能够反映光线传感器。
7. 现在将这个与其他的我们希望它们与之交互的元素进行连接。
a. 首先,改变图片名称的字体大小。
那么在光线测量值和字体大小间的转换,又一次的用到了转换器。
b. 然后,定位到ImageName的文本区域。
(请注意,我们对之前实现的进度条已经进行了绑定)。
XAML
<TextBlock Foreground="White" Grid.Column="1" Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Segoe UI" FontSize="{Binding ElementName=LightIntensityProgressBar, Path=Value, Mode=OneWay, Converter={StaticResource LuminosityToFontSizeConverter}}" Text="{Binding ImageName, Mode=OneWay}"/>
8. 转到转换器的后台代码实现。
C#
public class LuminosityToFontSizeConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object
parameter, System.Globalization.CultureInfo culture)
{
double d = System.Convert.ToDouble(value);
return 70.0 * d + 13.0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
9. 现在,将这个转换器声明成一个资源。
XAML
<Window.Resources>
<local:LuminosityConverter x:Key="LuminosityConverter" />
<local:LuminosityToFontSizeConverter
x:Key="LuminosityToFontSizeConverter" />
</Window.Resources>
10. 编译并运行代码。
当光线增强的时候,图片名称的文字应该变大。
现在将图片连接到光线指示器,并且允许图片使用灰白效果。
11. 我们从你的开始解决方案中,向PixelShaders添加XAML命名空间开始。
XAML
<Window x:Class="SensorHOL.MainWindow"
xmlns=https://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=https://schemas.microsoft.com/winfx/2006/xaml
xmlns:local="clr-namespace:SensorHOL"
xmlns:PixelShaders="clr-namespace:SensorHOL.PixelShaders"
Title="MainWindow" WindowState="Maximized" Background="Black">
12. 现在我们可以添加PixelShader元素,并且对第二张图片添加OpacityMask。
XAML
<Image Source="{Binding ImagePath}" Stretch="Uniform" Margin="10">
</Image>
<Image Source="{Binding ImagePath}" Stretch="Uniform" Margin="10">
<Image.Effect>
<PixelShaders:GrayscaleEffect DesaturationFactor="{Binding ElementName=LightIntensityProgressBar, Path=Value, Mode=OneWay, Converter={StaticResource InvertLuminosityConverter}}"/>
</Image.Effect>
<Image.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="{Binding ElementName=OverlayAdjustmentSlider, Path=Value}" Color="#0000"/>
<GradientStop Offset="{Binding ElementName=OverlayAdjustmentSlider, Path=Value}" Color="#F000"/>
</LinearGradientBrush>
</Image.OpacityMask>
</Image>
13. 同样,在光线测量值和PixelShader值之前,实现一个数据转换器。
C#
public class InvertLuminosityConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
double d = System.Convert.ToDouble(value);
return 1 - d;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
14. 和我们创建完转换器一样,我们还需要将其指定为一个资源。
XAML
<Window.Resources>
<local:LuminosityConverter x:Key="LuminosityConverter" />
<local:LuminosityToFontSizeConverter
x:Key="LuminosityToFontSizeConverter" />
<local:InvertLuminosityConverter x:Key="InvertLuminosityConverter" />
</Window.Resources>
15. 编译并运行你的代码。现在你应该已经可以使用滑块来控制图片,并且其中一部分的图片将与传感器进行交互,展示灰白的图片。
任务 7 – 将加速传感器与UI 用户界面进行整合(可选)
在这个任务中,你将会把3D加速传感器与UI用户界面进行连接。另外,还有左边的加速器指示器,右边的应用程序随每个传感器运动进行的旋转。
1. 首先,你要实现X,Y,Z轴的运动指示器:
a. 在MainWindow.xaml中,找到LightIntensityProgressBar。
b. 如果想要做这个,再添加3个指示器。
其实他们在代码上看很清楚,因为所有3个指示器都绑定到同一个属性,我们只需要一种方法来区分每个指示器。这个就是通过转换进程来关心这个问题的转换器元素的目的。
XAML
<TextBlock Grid.Column="0" Grid.Row="2" HorizontalAlignment="Center" Margin="4,0,4,10" VerticalAlignment="Center" Foreground="White" Text="LUM"/>
<ProgressBar x:Name="LightIntensityProgressBar" Width="10" HorizontalAlignment="Center" Margin="0,8,0,5" Maximum="1" Minimum="0" Orientation="Vertical" Style="{StaticResource SensorProgressBar}" Value="{Binding Path=LightSensor.Value.Intensity, Mode=OneWay, Converter={StaticResource LuminosityConverter}}"/>
<TextBlock Grid.Column="1" Grid.Row="2" HorizontalAlignment="Center" Margin="4,0,4,10" VerticalAlignment="Center" Foreground="White" Text="X"/>
<ProgressBar x:Name="AccelerometerX" Width="10" Grid.Column="1" HorizontalAlignment="Center" Margin="0,8,0,5" Maximum="1.3" Minimum="-1.3" Orientation="Vertical" Style="{StaticResource SensorProgressBar}" Value="{Binding Path=AcceleromaterSensor.Value, Mode=OneWay, Converter={StaticResource AccelerationConverter}, ConverterParameter=X}"/>
<TextBlock Grid.Column="2" Grid.Row="2" HorizontalAlignment="Center" Margin="4,0,4,10" VerticalAlignment="Center" Foreground="White" Text="Y"/>
<ProgressBar x:Name="AccelerometerY" Width="10" Grid.Column="2" HorizontalAlignment="Center" Margin="0,8,0,5" Maximum="1.3" Minimum="-1.3" Orientation="Vertical" Style="{StaticResource SensorProgressBar}" Value="{Binding Path=AcceleromaterSensor.Value, Mode=OneWay, Converter={StaticResource AccelerationConverter}, ConverterParameter=Y}"/>
<TextBlock Grid.Column="3" Grid.Row="2" HorizontalAlignment="Center" Margin="4,0,4,10" VerticalAlignment="Center" Foreground="White" Text="Z"/>
<ProgressBar x:Name="AccelerometerZ" Width="10" Grid.Column="3" HorizontalAlignment="Center" Margin="0,8,0,5" Maximum="1.3" Minimum="-1.3" Orientation="Vertical" Style="{StaticResource SensorProgressBar}" Value="{Binding Path=AcceleromaterSensor.Value, Mode=OneWay, Converter={StaticResource AccelerationConverter}, ConverterParameter=Z}"/>
2. 现在我们可以添加一个使用转换元素的数据转换器,但是首先我们需要添加下面的引用部分。
C#
using Microsoft.WindowsAPICodePack.Sensors;
3. 添加下面的代码。
C#
public class AccelerationConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object
parameter, System.Globalization.CultureInfo culture)
{
Accelerometer3D.AccelerationAxis? axis =
(Accelerometer3D.AccelerationAxis?)Enum.Parse(typeof(Acceleromete r3D.AccelerationAxis), parameter as string);
if (axis.HasValue)
{
var a3d = value as Accelerometer3D.Acceleration3D;
if (a3d != null)
return a3d[axis.Value];
}
return 0.0;
}
public object ConvertBack(object value, Type targetType, object
parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
4. 添加数据转换器资源声明。
XAML
<Window.Resources>
<local:LuminosityConverter x:Key="LuminosityConverter" />
<local:LuminosityToFontSizeConverter x:Key="LuminosityToFontSizeConverter" />
<local:InvertLuminosityConverter x:Key="InvertLuminosityConverter" />
<local:AccelerationConverter x:Key="AccelerationConverter" /> </Window.Resources>
现在我们就可以使用3D加速器传感器,和屏幕上的旋转元素了。
我们使用WPF中的使用角度来进行旋转。转换器将加速器的X轴和Y轴转换成角度。为了保证旋转有效的工作,必须将它设置在元素的中央。这就是为什么我们还有一个专门计算元素中央位置的转换器。
5. 为每一个图片添加下面的代码片段。
XAML
<Image.RenderTransform>
<RotateTransform
Angle="{Binding Path=AcceleromaterSensor.Value, Mode=OneWay, Converter={StaticResource DegreesFromAcceleratorConverter}}" CenterX="{Binding RelativeSource={RelativeSource AncestorType={x:Type Image}, AncestorLevel=1}, Path=ActualWidth, Converter={StaticResource DoubleSplitConverter}}"
CenterY="{Binding RelativeSource={RelativeSource AncestorType={x:Type Image}, AncestorLevel=1}, Path=ActualHeight, Converter={StaticResource DoubleSplitConverter}}" />
</Image.RenderTransform>
6. 在Slider元素中,添加下面的代码片段(与上面的代码非常相似)。
XAML
<Slider.RenderTransform>
<RotateTransform
Angle="{Binding Path=AcceleromaterSensor.Value, Mode=OneWay, Converter={StaticResource DegreesFromAcceleratorConverter}}"
CenterX="{Binding RelativeSource={RelativeSource AncestorType={x:Type Slider}, AncestorLevel=1}, Path=ActualWidth, Converter={StaticResource DoubleSplitConverter}}"
CenterY="{Binding RelativeSource={RelativeSource AncestorType={x:Type Slider}, AncestorLevel=1}, Path=ActualHeight, Converter={StaticResource DoubleSplitConverter}}"/>
</Slider.RenderTransform>
7. 实现第一个转换器,DegreesFromAcceleratorConverter。
C#
public class DegreesFromAcceleratorConverter : IValueConverter
{
private double _prevX;
private double _prevY;
private const double _histCoeff = 0.6;
private const double _threshold = 0.1;
private bool _prevValuesInit;
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var a3d = value as Accelerometer3D.Acceleration3D;
if (a3d != null)
{
double x = a3d[Accelerometer3D.AccelerationAxis.X];
double y = a3d[Accelerometer3D.AccelerationAxis.Y];
if (!_prevValuesInit)
{
_prevValuesInit = true;
_prevX = x;
_prevY = y;
}
if (Math.Abs(_prevX - x) < _threshold)
x = _prevX;
if (Math.Abs(_prevY - y) < _threshold)
y = _prevY;
_prevX = (_histCoeff * _prevX) + (1.0 - _histCoeff) * x;
_prevY = (_histCoeff * _prevY) + (1.0 - _histCoeff) * y;
double radians = Math.Atan2(_prevX, -_prevY);
return radians * 180 / Math.PI;
}
return 0.0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
8. 现在添加第二个转换器。
C#
public class DoubleSplitConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double doubleValue = System.Convert.ToDouble(value);
return doubleValue/2.0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
9. 所有剩下的你需要做的,就是将那些转换器声明成资源。
XAML
<local:DoubleSplitConverter x:Key="DoubleSplitConverter" /> <local:DegreesFromAcceleratorConverter x:Key="DegreesFromAcceleratorConverter" />
10. 编译并运行你的代码。
你现在应该可以使用滑块来单独控制每个图片的旋转了。
你可以在Final解决方案文件夹中找到这个项目的最终版。做得好!
摘要
将Windows 7传感器支持整合到你的应用程序中,通过使应用程序能够对周围环境作出更快更好的反应,将大大提高你的应用程序的机会。在这个实验中,你已经创建了一个WPF应用程序,并且通过整合不同的传感器,制造了令人兴奋的UI用户界面体验。