Exercise 1: Integrating the Sensor API into a WPF Application

Task 1 – Prepare a WPF Project for Sensor Integration

  1. Start Visual Studio 2008 and open the Starter solution that contains one WPF application. The solution is located in your Starter folder.

    Figure 2

    Starter solution

    The WPF project, SensorHOL, is already bundled with the premade Grayscale PixelShader effect that we will use later in this Lab, and a resource dictionary that includes several resources used by future WPF controls.

  2. Add a new class named SensorViewModel to the project. MainWindow view will be data-binded to its properties. By using this pattern, the View is relieved from triggering its own updates and instead passively waits for the ViewModel to change.

    Figure 3

    Create SensorViewModel class

  3. Implement the INotifyPropertyChanged interface in the SensorViewModel class.

  4. Add the following code (we call this method whenever we wish to raise the notification):

  5. In order to connect this ViewModel with the View, add the line just after the InitializeComponent(), MainWindow.xaml.cs.

Test your code to make sure it compiled.

Task 2 – Add Layout to the WPF Application

Figure 4

Sensor HOL basic Layout

In this task, you will design the MainWindow layout. It will contain 3 parts:

  • Top part: “Browse…” button and the photo name
  • Left part: Several Sensor indicators
  • Right part: An image.
  • A slider divides the photo; sensor data in each portion of the image will display differently depending on the current light intensity.
  • Open the MainWindow.xaml file.
  • Grid is a powerful layout mechanism provided by WPF. It will be our sole means for designing the layouts in this lab.
  • Start by adding rows and columns definitions.

  • Next, add the right part of the Grid, just after the columns definitions. This Grid will contain the image and slider.

  • Add two image controls inside the “OverlayContentGrid.Each will render a different effect and they are overlaid

  • Next, add the Slider control inside the Grid. Note that the slider width and height are data bound to the grid layout.

  • Let’s focus on the left part of the layout: add another Grid, just after “OverlayContentGrid.

  • Inside that Grid, you will add a sensor indicator control and the rest of the Sensor indicators. The inner Grid holds the “Sensor Data” caption.
  • Create a border with a caption.

  • The last part consists solely of a Grid containing a button and a caption.

  • Now, add the code to handle the “Browse…” Button click event to MainWindow view.

  • Your MainWindow XAML should look like this:

  • Test your code to make sure it compiles.

Task 3 – Add Image Specific Logic

In the previous task, you designed the layout of the application, now it is time to pour content into it.

  1. Open SensorViewModel. After you add a new property, the user will be able to browse to a file and this property will contain his image selection. .
  2. Add the NotifyChange method. Each time this property is changed, the View will respond to this change.

  3. Add the System.IO namespace to SensorViewModel.

  4. Add a read-only property that requests the file name from the ImagePath property.

  5. Now, we need to make sure that when ImagePath has changed, the UI binded to ImageName changes as well.
  6. Add a call to NotifyChange, this time for the ImageName property.

  7. Now let’s implement: Go back to the view and register the necessary binding.

  8. You can now data-bind the file name label. (Note: the binding is one-way because the property is read-only)

  9. The last piece of this puzzle is to set the ViewModel with a path to an image. To do this, you must go to the code-behind and implement the Button_Click method. This code will open the standard Windows Open File dialog. If the user decides to select an image, it is just a matter of setting the ViewModel with his selection.
  10. First, add a namespace for the OpenFIleDialog.

  11. Now, you can implement Button_Click.

  12. Compile and test your code:
    1. Browse for an image; you should see it on the right side of the screen.
    2. Browse for more images and note that the UI changes correspondingly.

Task 4 – Setting up the Sensor Helper

  1. Expand References under the SensorHOL project.
  2. Right-click on References and click Add Reference.
  3. Click on the Browse tab and navigate to the folder where Microsoft.WindowsAPICodePack.Sensors.dll is located.
  4. Select the DLL and click OK.
  5. Create a new class named SensorHelper in a new file: SensorHelper.cs.
  6. Define the class like this:The class' SensorTypetype parameter is the Sensor-derived sensor class (for example, AmbientLightSensoror Accelerometer3D).

  7. Paste the following code into the class:

  8. Initialize() requests a list of sensors from the Sensor Manager which are of type SensorType. When SensorHelper is used, SensorType would be either AmbientLightSensor or Acceleroemter3DSensor.
  9. Since the user account might not have permissions to use the sensors, we'll call SensorManager.RequestPermissions(), specifying the parent window handle, whether the function is synchronous (modal), and the list of sensors for which we request access. This will pop up a dialog like this:

    Behind-the-scenes, the CodePack API wrapper maps each Sensor-derived type to a sensor type GUID that the COM API expects. The mapping information is provided by an attribute on the class:

  10. Initialize() then iterates over each found sensor and calls HandleNewSensor(). Finally, it subscribes to the SensorsChanged event that fires when new sensors are attached or existing sensors are removed.
  11. Paste the following code into the SensorHelper class:

  12. The OnSensorsChanged method is the event handler for the SensorsChanged event we subscribed to in Initialize(). If a new sensor is attached, we obtain a base-class Sensor object from its instance GUID property (SensorId) which we get from the event arguments.Sensors have three GUIDs:

    Instance: Identifies the sensor instance uniquely

    Category: For example, environment, mechanical, electrical

    Type: For example, temperature, humidity, voltage, current, 3D accelerometer

    We then check if the sensor object is of the proper type. If so, we call HandleNewSensor().

    This approach allows the application to use sensors that are attached later.

    OnSensorsChanged() also handles sensor removal. It clears a variable that indicates what sensor is in use. This allows another sensor to be used if it becomes available.

  13. Sensors have many other properties such as Manufacturer, Model, SerialNumber,etc.
  14. These are standard properties. Various sensors might provide additional properties.
  15. HandleNewSensor() checks for the sensor's state. Even if a sensor is attached, it may not be ready to use. State tells you if the sensor is ready or the reason it's not. The hardware might be initializing, or a fault condition might exist, or the user might not have permission to use the sensor. If the sensor is ready to be used, we call PrepareSensor(). We call subscribe to the sensor's StateChanged event, which fires when the state changes. We could later use it if it becomes ready and we don't already have a sensor.
  16. Paste the following code into the SensorHelper class:

  17. OnSensorStateChanged() fires when a sensor state changes. If it's ready, we call PrepareSensor(). If it changes state to something other than Ready, we check if it's the one sensor we're using. If so, we unsubscribe and clear the variable indicating that a sensor is in use.
  18. PrepareSensor() sets a variable indicating that a sensor is in use, so that another sensor of the same type won't take over if it is attached or becomes ready in the future.

    The function then subscribes to the DataReportChangedevent.

    TryUpdateData() is called to force the sensor to generate a new data report. Some sensors might generate data reports periodically, while others will generate a data report once the measured value changes significantly enough. By calling TryUpdateData(), we initialize the UI with an initial value immediately.

  19. Paste the following code into the SensorHelper class:

    The OnDataUpdated() method handles the DataUpdated event.

    This event fires when the sensor has a new data report available.

    The data is accessed by getting one or more properties (which are different for each sensor). To access a property, the sensor needs to be cast to the appropriate derived type.

  20. Paste the following code into the SensorHelper class:

    This code supports the integration of SensorHelperwith the UI code.

Task 5 – Integrating ViewModel and the Sensor Helper

  1. Open SensorViewModel.cs and create a property with a private field that will expose a Light Sensor.

  2. Now we will do the same, but for the Accelerometer Sensor.

  3. Add a constructor for SensorViewModel, in which we will set up the sensors

  4. Compile and test your code.You should be able to run and integrate with the sensors.

Task 6 – Add Light Sensor Interaction with the UI

In the next task, we will make use of the light intensity sensor. Three separate elements will interact with it:

  • LUM indicator: A progress bar that measure the light intensity
  • The Image: One-half of the image will become gray scaled as the intensity increases.
  • The Image Name: Font size will increase to support readability on high light intensity.
  • To add our first sensor Indicator, go to MainWindow.xaml and locate the left part layout grid (the one that contains a TextBlock, “Sensor Data”).

  • For our binding to work, we need to create a data converter between the values of the Sensor to the progress bar control. We do that by adding a converter class inside MainWIndow.xaml.cs.

  • After we have created the LuminosityConverter class, we need to declare it as a resource.

    Go back to MainWindow.xaml and the “local” XAML namespace declaration of the same namespace as the converter.

  • Add a Resources section to the Window control.

  • Inside the resources section, declare the Converter resource.

  • Compile and run the application.You should see the LUM indicator responding to the Light Sensor.
  • Now connect this interaction to the rest of the elements with which we wished them to interact:
    1. First, change the font size of the image name. Once again a converter is needed to translate between the light values and font sizes.
    2. Then, locate the ImageName text block. (Note that we are binding the progress bar we implemented earlier).

  • Go to code-behind the implement this converter

  • Now declare this converter as a resource.

  • Compile and run the code.The Image Name text should increase whenever the light intensifies.
  • Now connect the image to the light indicator and apply a grayscale pixelshader effect to the image.
  • Start by adding a XAML namespace to the PixelShaders that are located on your Starter solution.

  • Now can add the PixelShader element, and an OpacityMask to the second Image.

  • Again, implement a data converter between the light values and PixelShader values.

  • As usual after we create the converter, we need to assign it as a resource.

  • Compile and run your code.Now you should be able to play with slider and have a portion of the image turned to grayscale due to interaction with the sensor.

Task 7 – Add Accelerometer Sensor Interaction with the UI (Optional)

In this task, you will connect a 3D Accelerometer sensor to the UI. In addition to the accelerometer indicators on the left, the right part of the application rotates with each sensor movement.

  1. First you will implement the X,Y,Z axis movements indicators:
    1. Inside MainWindow.xaml, locate the LightIntensityProgressBar.
    2. to this, add 3 more indicators.Since it is clear from the code that all 3 indicators bind to the same property, we need a way to distinguish between each indicator. that is the purpose of the Converter parameter that is used throughout the conversion process takes care of this.

  2. Now we can add a data converter that makes use of the converter parameter, but first add the following using statement.

  3. Add the following code snippet.

  4. Add the data converter resource declaration.

  5. Now we can make use of the 3D accelerometer sensor and rotate elements on the screen.

    We use WPF’s rotate transform that uses degrees. A converter converts the X- and Y-axis of the Accelerometer to degrees. In order for the rotation to work properly, it must be centered on the middle of the element. That why we have the second converter that calculates the center of the element.

  6. Add the following snippet to each of the images.

  7. Add the following snippet to the Slider element (very similar to the one above).

  8. Implement the first converter, DegreesFromAcceleratorConverter.

  9. Now add the second converter.

  10. All you have left to do is declare those converters as resources.

  11. Compile and run your code. You should now be able to rotate the image along with it’s slider.

  12. You can find the final version of this project in the Final solution folder. Well Done!