windows phone 7 instance: ruler

     A ruler is something that is really handy to have on rare occasions, yet something that you almost never have when you need it. With the Ruler app, you can measure anything on-the-go with the phone already in your pocket!
Ruler shows a standard display of inches (divided into 16ths) and centimeters (divided into millimeters) and enables you to tap and drag on its surface to place a marker line that reveals the precise measurement at that point. Of course, unless you’re measuring something shorter than your phone’s screen, you’ll need some way to move to later  sections of the ruler. This is done with left and right buttons. Therefore, the best approach to measure something long is as follows:
    1. Align the starting edge of the object with the starting edge of the phone screen.
    2. Tap the screen toward the end of the visible ruler to place the marker line on the screen.
    3. Place an available finger on the object and align it with the marker line (to remember that precise location).
    4. Tap the right button to advance the ruler. This moves the marker line (along with the rest of the ruler) toward the start of the screen.
    5. Slide your phone so the new position of the marker line is aligned with your finger that remembered the old position.

    6. Repeat steps 2–5 until you’ve reached the end of the object.

     Ruler also has a calibration button for adjusting the space between its lines, as not all Windows Phone screens are the same size.
    There are several notable things about this app. Among other things, it is the first landscape-only app in this book, it is the first one to use a canvas, and the first to use some vector graphics (albeit simple ones). This chapter examines canvas and vector graphics features before proceeding to the Ruler app.


    MainPage.xaml:

    <!-- Copyright (c) Adam Nathan.  All rights reserved.
     By purchasing the book that this source code belongs to, you may use and modify this code for commercial and non-commercial applications, but you may not publish the source code.
     THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES. -->
<phone:PhoneApplicationPage 
    x:Class="WindowsPhoneApp.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:local="clr-namespace:WindowsPhoneApp"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    SupportedOrientations="Landscape" Orientation="Landscape">
  <!-- The root 1x1 grid holds several overlapping panels in the same cell -->
  <Grid x:Name="LayoutRoot">


    <!-- Holds the lines and their labels -->
    <Canvas x:Name="RulerCanvas"/>
    
    <!-- Interactive canvas listening for taps and drags -->
    <Canvas Background="Transparent" MouseMove="InteractiveCanvas_MouseTapOrDrag"
                MouseLeftButtonDown="InteractiveCanvas_MouseTapOrDrag">
      <!-- The marker line that gets dragged -->
      <Line x:Name="ExactMeasurementLine" Y1="7" Y2="473" StrokeThickness="12"
            Stroke="{StaticResource PhoneAccentBrush}" Opacity=".8"
            StrokeEndLineCap="Triangle" StrokeStartLineCap="Triangle"/>
    </Canvas>


    <!-- Contains buttons (separate so button taps don't move marker line) -->
    <Canvas x:Name="ButtonsCanvas">
      <!-- right -->
      <Button Canvas.Left="698" Canvas.Top="189" Click="LeftOrRightButton_Click"
              Padding="12" Background="Black" >
        <Image Source="Shared/Images/appbar.right.png"/>
      </Button>


      <!-- left -->
      <RepeatButton x:Name="LeftButton" Canvas.Left="0" Canvas.Top="189"
                    Padding="12" Click="LeftOrRightButton_Click" Background="Black"
                    Visibility="Collapsed" >
        <Image Source="Shared/Images/appbar.left.png"/>
      </RepeatButton>


      <!-- calibrate -->
      <Button Canvas.Top="270" Canvas.Left="305" Click="CalibrateButton_Click"
              Padding="0" Background="Black" >
        <StackPanel Orientation="Horizontal">
          <Image Source="Shared/Images/appbar.settings.png"/>
          <TextBlock Foreground="White" Text="calibrate" Padding="0,4,12,0"/>
        </StackPanel>
      </Button>
    </Canvas>
    
    <!-- A direct child of the grid so it's easily centered -->
    <TextBlock x:Name="ExactMeasurementTextBlock" FontSize="60" Margin="0,0,0,8"
               HorizontalAlignment="Center" VerticalAlignment="Center"
               IsHitTestVisible="False"
               Foreground="{StaticResource PhoneSubtleBrush}"/>


    <!-- The UI for "calibration mode" -->
    <Grid x:Name="CalibrationPanel" Background="Transparent"
          Visibility="Collapsed">
      <!-- Explanation of slider -->
      <TextBlock Text="Adjust scale" Margin="24" Padding="0,0,0,134" 
                 VerticalAlignment="Center" HorizontalAlignment="Right"
                 Foreground="{StaticResource PhoneSubtleBrush}"/>
      <!-- The slider that adjusts the line spacing -->
      <Slider x:Name="SpacingSlider" Minimum="12" Maximum="24" LargeChange=".2"
              VerticalAlignment="Center" Margin="{StaticResource PhoneMargin}"
              ValueChanged="SpacingSlider_ValueChanged" />
      <!-- A pair of done & reset buttons in the bottom-right corner -->
      <StackPanel Orientation="Horizontal" Margin="0,0,12,88"
                  VerticalAlignment="Bottom" HorizontalAlignment="Right">
        <Button Content="done" MinWidth="200" 
                Background="Black" Foreground="White"
                Click="CalibrationDoneButton_Click"/>
        <Button Content="reset" MinWidth="200" 
                Background="Black" Foreground="White"
                Click="CalibrationResetButton_Click"/>
      </StackPanel>
    </Grid>
  </Grid>
</phone:PhoneApplicationPage>


    MainPage.xaml.cs:

    // Copyright (c) Adam Nathan.  All rights reserved.
//
// By purchasing the book that this source code belongs to, you may use and
// modify this code for commercial and non-commercial applications, but you
// may not publish the source code.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;


namespace WindowsPhoneApp
{
  public partial class MainPage : PhoneApplicationPage
  {
    // Remember the calibration setting
    Setting<double> inch16thSpacing = new Setting<double>(
      "Inch16thSpacing", Constants.DEFAULT_INCH_16TH_SPACING);


    // Two more settings to remember the current state
    Setting<double> exactMeasurementPosition =
      new Setting<double>("ExactMeasurementPosition", 0);
    Setting<double> horizontalOffset =
      new Setting<double>("HorizontalOffset", 0);


    // State to restore after exiting the temporary calibration mode
    double preCalibrationScrollOffset;
    double preCalibrationSpacing;


    public MainPage()
    {
      InitializeComponent();
    }


    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
      base.OnNavigatedTo(e);


      // Refresh the UI based on the persisted settings
      DrawRuler();
      if (this.horizontalOffset.Value > 0)
        this.LeftButton.Visibility = Visibility.Visible;
      this.ExactMeasurementLine.X1 = this.exactMeasurementPosition.Value;
      this.ExactMeasurementLine.X2 = this.exactMeasurementPosition.Value;                                     
      UpdateExactMeasurementText();
      this.SpacingSlider.Value = this.inch16thSpacing.Value;
    }


    protected override void OnNavigatedFrom(NavigationEventArgs e)
    {
      base.OnNavigatedFrom(e);


      // Undo the offset change from calibration mode and save the original one
      if (this.CalibrationPanel.Visibility == Visibility.Visible)
        this.horizontalOffset.Value = this.preCalibrationScrollOffset;
    }


    // Override the behavior of the hardware Back button
    protected override void OnBackKeyPress(CancelEventArgs e)
    {
      base.OnBackKeyPress(e);


      if (this.CalibrationPanel.Visibility == Visibility.Visible)
      {
        // "Click" the done button
        CalibrationDoneButton_Click(null, null);
        // Cancel exiting the app
        e.Cancel = true;
      }
    }


    void SpacingSlider_ValueChanged(object sender,
      RoutedPropertyChangedEventArgs<double> e)
    {
      // Guard against null when raised from within InitializeComponent
      if (this.SpacingSlider != null)
      {
        this.inch16thSpacing.Value = this.SpacingSlider.Value;
        DrawRuler();
      }
    }


    void LeftOrRightButton_Click(object sender, RoutedEventArgs e)
    {
      double delta;
      if (sender == this.LeftButton)
      {
        // Scroll left, and don't go below 0
        delta = -1 * Math.Min(Constants.DEFAULT_SCROLL_AMOUNT,
                              this.horizontalOffset.Value);
      }
      else
      {
        // Scroll right
        delta = Constants.DEFAULT_SCROLL_AMOUNT;
        // If the line appears to be used, ensure it moves close to the start
        if (this.ExactMeasurementLine.X1 > 20)
          delta = this.ExactMeasurementLine.X1 - 20;
      }


      // Perform the virtual scrolling
      this.horizontalOffset.Value += delta;


      // Keep the line in the correct (now shifted) position
      this.ExactMeasurementLine.X1 -= delta;
      this.ExactMeasurementLine.X2 -= delta;
      this.exactMeasurementPosition.Value -= delta;


      if (this.horizontalOffset.Value == 0)
        this.LeftButton.Visibility = Visibility.Collapsed;
      else
        this.LeftButton.Visibility = Visibility.Visible;


      DrawRuler();
    }


    void CalibrateButton_Click(object sender, RoutedEventArgs e)
    {
      // Hide non-calibration pieces of UI and show the calibration panel
      this.ButtonsCanvas.Visibility = Visibility.Collapsed;
      this.ExactMeasurementTextBlock.Visibility = Visibility.Collapsed;
      this.ExactMeasurementLine.Visibility = Visibility.Collapsed;
      this.CalibrationPanel.Visibility = Visibility.Visible;


      // Draw the ruler in "calibration mode" with fewer lines & a fixed position
      this.LayoutRoot.Background =
        Application.Current.Resources["PhoneChromeBrush"] as Brush;
      // Save the current position and spacing
      this.preCalibrationScrollOffset = this.horizontalOffset.Value;
      this.preCalibrationSpacing = this.inch16thSpacing.Value;
      this.horizontalOffset.Value = 0;
      DrawRuler();
    }


    void CalibrationDoneButton_Click(object sender, RoutedEventArgs e)
    {
      // Restore the non-calibration pieces of UI and hide the calibration panel
      this.ButtonsCanvas.Visibility = Visibility.Visible;
      this.ExactMeasurementTextBlock.Visibility = Visibility.Visible;
      this.ExactMeasurementLine.Visibility = Visibility.Visible;
      this.CalibrationPanel.Visibility = Visibility.Collapsed;


      // Enter "normal mode"
      this.LayoutRoot.Background = null;


      if (this.inch16thSpacing.Value == this.preCalibrationSpacing)
      {
        // The spacing hasn't changed, so restore the UI to its previous state
        this.horizontalOffset.Value = this.preCalibrationScrollOffset;
      }
      else
      {
        // The spacing has changed, so keep the offset at 0 and reset the UI
        UpdateExactMeasurementText();
        this.LeftButton.Visibility = Visibility.Collapsed;
      }


      DrawRuler();
    }


    void CalibrationResetButton_Click(object sender, RoutedEventArgs e)
    {
      // This invokes CalibrationSlider_ValueChanged,
      // which does the rest of the work
      this.SpacingSlider.Value = this.inch16thSpacing.DefaultValue;
    }


    void InteractiveCanvas_MouseTapOrDrag(object sender, MouseEventArgs e)
    {
      // Get the finger position relative to the landscape-oriented page
      double x = e.GetPosition(this).X;


      // Move the line and save this position
      this.ExactMeasurementLine.X1 = x;
      this.ExactMeasurementLine.X2 = x;
      this.exactMeasurementPosition.Value = x;


      UpdateExactMeasurementText();
    }


    void UpdateExactMeasurementText()
    {
      double inches = (this.horizontalOffset.Value + this.ExactMeasurementLine.X1)
                      / (this.inch16thSpacing.Value * 16);
      double cm = inches * Constants.CONVERT_IN_TO_CM;
      this.ExactMeasurementTextBlock.Text = inches.ToString("0.00") + " in (" 
                                            + cm.ToString("0.00") + " cm)";
    }


    void DrawRuler()
    {
      // Remove all elements and draw everything over again
      this.RulerCanvas.Children.Clear();


      double mmSpacing = this.inch16thSpacing.Value
                         * Constants.CONVERT_INCH_16TH_SPACING_TO_MM_SPACING;


      // By default, draw until we reach the end of the screen
      double inch16thXLimit = Constants.SCREEN_WIDTH + Constants.LINE_WIDTH;
      double cmXLimit = Constants.SCREEN_WIDTH + Constants.LINE_WIDTH;


      if (this.CalibrationPanel.Visibility == Visibility.Visible)
      {
        // In "calibration mode", only draw up to 1 inch and 2 cm, which gives
        // better performance while dragging the slider
        inch16thXLimit = 16 * this.inch16thSpacing.Value - Constants.LINE_WIDTH;
        cmXLimit = 10 * mmSpacing - Constants.LINE_WIDTH;
      }


      // Note: Behaves badly when horizontalOffset becomes unrealistically huge
      int inch16thLineIndex = (int)(this.horizontalOffset.Value /
                                    this.inch16thSpacing.Value);
      int mmLineIndex = (int)(this.horizontalOffset.Value / mmSpacing);


      // Render each inch number label
      double x = 0;
      int index = inch16thLineIndex;
      while (x < inch16thXLimit)
      {
        x = DrawNumber(index / 16, true);
        index += 16;
      }


      // Render each centimeter number label
      x = 0;
      index = mmLineIndex;
      while (x < cmXLimit)
      {
        x = DrawNumber(index / 10, false);
        index += 10;
      }


      // Render each 16th-of-an-inch line
      double inchLineX = -Constants.LINE_WIDTH;
      while (inchLineX <= inch16thXLimit)
      {
        inchLineX = Draw16thInchLine(inch16thLineIndex);
        inch16thLineIndex++;
      }


      // Render each millimeter line
      double mmLineX = -Constants.LINE_WIDTH;
      while (mmLineX <= cmXLimit)
      {
        mmLineX = DrawMillimeterLine(mmLineIndex);
        mmLineIndex++;
      }
    }


    double Draw16thInchLine(int lineIndex)
    {
      // Determine the correct horizontal position from the line index
      double x = (lineIndex * this.inch16thSpacing.Value) 
                 - this.horizontalOffset.Value;


      // Create and position the line, and add it to the canvas
      Line line = new Line {
        Stroke = Application.Current.Resources["PhoneForegroundBrush"] as Brush,
        StrokeThickness = Constants.LINE_WIDTH };
      Canvas.SetLeft(line, x);
      this.RulerCanvas.Children.Add(line);


      // Vary the length based on whether it's a whole inch, half inch, ...
      if (lineIndex % 16 == 0)
        line.Y2 = Constants.INCH_LINE_LENGTH;
      else if (lineIndex % 8 == 0)
        line.Y2 = Constants.INCH_HALF_LINE_LENGTH;
      else if (lineIndex % 4 == 0)
        line.Y2 = Constants.INCH_4TH_LINE_LENGTH;
      else if (lineIndex % 2 == 0)
        line.Y2 = Constants.INCH_8TH_LINE_LENGTH;
      else
        line.Y2 = Constants.INCH_16TH_LINE_LENGTH;


      return x;
    }


    double DrawMillimeterLine(int lineIndex)
    {
      // Determine the correct horizontal position from the line index
      double x = (lineIndex * this.inch16thSpacing.Value *
                  Constants.CONVERT_INCH_16TH_SPACING_TO_MM_SPACING) 
                  - this.horizontalOffset.Value;


      // Create and position the line, and add it to the canvas
      Line line = new Line { Y1 = Constants.SCREEN_HEIGHT,
        Stroke = Application.Current.Resources["PhoneForegroundBrush"] as Brush,
        StrokeThickness = Constants.LINE_WIDTH };
      Canvas.SetLeft(line, x);
      this.RulerCanvas.Children.Add(line);


      // Vary the length based on whether it's a whole cm, half cm, ...
      if (lineIndex % 10 == 0)
        line.Y2 = Constants.SCREEN_HEIGHT - Constants.CENTIMETER_LINE_LENGTH;
      else if (lineIndex % 5 == 0)
        line.Y2 = Constants.SCREEN_HEIGHT - Constants.CENTIMETER_HALF_LINE_LENGTH;
      else
        line.Y2 = Constants.SCREEN_HEIGHT - Constants.MILLIMETER_LINE_LENGTH;


      return x;
    }


    double DrawNumber(int num, bool isInch)
    {
      // Determine the correct horizontal position of the line
      // corresponding to the inch or cm number
      double x;
      if (isInch)
        x = (num * 16 * this.inch16thSpacing.Value) 
            - this.horizontalOffset.Value;
      else
        x = (num * 10 * this.inch16thSpacing.Value * 
             Constants.CONVERT_INCH_16TH_SPACING_TO_MM_SPACING) 
             - this.horizontalOffset.Value;


      if (num == 0)
      {
        // Don't actually render a "0"... put an "in" or "cm" label instead
        TextBlock textBlock = new TextBlock();
        textBlock.Text = isInch ? "in" : "cm";
        Canvas.SetTop(textBlock, isInch ? 98 : 382);
        Canvas.SetLeft(textBlock, x + Constants.LABEL_X);
        this.RulerCanvas.Children.Add(textBlock);
      }
      else
      {
        // Use a content control to enable centering the number on the line
        ContentControl container = new ContentControl {
          Width = this.inch16thSpacing.Value * 16, // Wide enough both in and cm
          HorizontalContentAlignment = HorizontalAlignment.Center };


        // This left position centers the content control on x
        Canvas.SetLeft(container, x - container.Width / 2);
        Canvas.SetTop(container, isInch ? 56 : Constants.SCREEN_HEIGHT - 110);
        this.RulerCanvas.Children.Add(container);


        // Add the text block to the content control, which centers its content
        TextBlock textBlock = new TextBlock { Text = num.ToString(),
                                              FontSize = isInch ? 80 : 40 };
        container.Content = textBlock;
      }


      return x;
    }
  }
}


   Shared/Settings/Setting.cs:

    // Copyright (c) Adam Nathan.  All rights reserved.
//
// By purchasing the book that this source code belongs to, you may use and
// modify this code for commercial and non-commercial applications, but you
// may not publish the source code.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
using System.IO.IsolatedStorage;


namespace WindowsPhoneApp
{
  // Encapsulates a key/value pair stored in Isolated Storage ApplicationSettings
  public class Setting<T>
  {
    string name;
    T value;
    T defaultValue;
    bool hasValue;


    public Setting(string name, T defaultValue)
    {
      this.name = name;
      this.defaultValue = defaultValue;
    }


    public T Value
    {
      get
      {
        // Check for the cached value
        if (!this.hasValue)
        {
          // Try to get the value from Isolated Storage
          if (!IsolatedStorageSettings.ApplicationSettings.TryGetValue(
                this.name, out this.value))
          {
            // It hasn't been set yet
            this.value = this.defaultValue;
            IsolatedStorageSettings.ApplicationSettings[this.name] = this.value;
          }
          this.hasValue = true;
        }


        return this.value;
      }


      set
      {
        // Save the value to Isolated Storage
        IsolatedStorageSettings.ApplicationSettings[this.name] = value;
        this.value = value;
        this.hasValue = true;
      }
    }


    public T DefaultValue
    {
      get { return this.defaultValue; }
    }


    // "Clear" cached value:
    public void ForceRefresh()
    {
      this.hasValue = false;
    }
  }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值