Calculator Demo -- Part 6

For the sake of organization and time, I have completed the animations as promised and I added all the other buttons to the grid. However, I only fully implemented the "+/-" operation because I thought it best to visit each operation in the next part.

  • Rendering the buttons
    • XAML:
      <Button Name="BPM" Click="OperBtn_Click" Background="Darkgray" Style="{StaticResource DigitBtn}"  Grid.Column="6" Grid.Row="5" >+/-</Button>
      
    • C#:
      Button BPM = new Button();
      BPM.Name = "BPM";
      BPM.Click += new RoutedEventHandler(OperBtn_Click);
      BPM.Background = Brushes.DarkGray;
      BPM.Style = DigitBtn; //try without resources
      Grid.SetColumn(BPM, 6);
      Grid.SetRow(BPM, 5);
      BPM.Content = "+/-";
      MyGrid.Children.Add(BPM);
      
    • Notes:

      This is exactly as the buttons I looked at previously except with a different event handler: BPM.Click += new RoutedEventHandler(OperBtn_Click);. Also, when doing some of the other operations there is a tooltip property set (eg. BMemPlus.ToolTip = "Add To Memory"; for the "M+" button).

I wanted the "+/-" button to work as expected so I followed the Click event:

  • OperBtn_Click
    • C#:
          private void OperBtn_Click(object sender, RoutedEventArgs e)
          {
              ProcessOperation(((Button)sender).Name.ToString());
          }
          
    • Notes:

      All the buttons have their name properties set. In the case of the "+/-" button we have BPM.Name = "BPM";. When the above handler is invoked it will then pass the name of the button as an argument to the ProcessOperation method.

  • ProcessOperation
    • C#:
          private void ProcessOperation(string s)
          {
              Double d = 0.0;
              switch (s)
              {
                  case "BPM":
                  LastOper = Operation.Negate;
                  LastValue = Display;
                  CalcResults();
                  LastValue = Display;
                  EraseDisplay = true;
                  LastOper = Operation.None;
                  break;
                  ...
              }
          }
          
    • Notes:

      Here we see what happens when calling ProcessOperation("BPM");. The variable "d" is not applicable to this case. First, the most recently clicked operation is saved into "LastOper" and the most recent value displayed is saved into "LastValue". By doing this before the calculation, the program is overriding whatever operation was clicked beforehand (that means, if you try to do "2*-2" by clicking "2,*,2,+/-" the multiplication will be forgotten). "LastValue" and "LastOper" deserve a closer look later. Next, the calculation is performed by calling "CalcResults". When the calculation is completed, the current display is again saved however the operation is set to null which is force other operations to not negate again. It is very important to do it this way because this calculator works with infix notation. When I first saw EraseDisplay = true; I thought it meant to actually erase the display but that doesn't make sense. What it is actually doing is taking a note so that the next operation will know whether it should erase the display (in the case of "ProcessKey") or whether to simply treat the display as empty (as in several cases of "ProcessOperation").

  • Operation LastOper
    • C#:
          private enum Operation
          {
              None,
              Devide,
              Multiply,
              Subtract,
              Add,
              Percent,
              Sqrt,
              OneX,
              Negate
          }
          private Operation LastOper;
          
    • Notes:

      I've used enums before for setting many properties in but the Alarm Clock Sample and this Calculator Demo but this is the first time I've really seen how they are useful. Here, they set us up so it will be very clear which operation is being used and we don't need to deal with strings. The compiler gives them numbers which is good for computers and we get a descriptive name which is good for us.

  • LastValue
    • C#:
          private string LastValue
          {
              get
              {
                  if (_last_val == string.Empty)
                  return "0";
                  return _last_val;
              }
              set
              {
                  _last_val = value;
              }
          }
          
    • Notes:

      This is a very simple property declaration but it shows us exactly why getters and setters are useful. This is a calculator application and it's not very mathematical to show an empty display area so it checks if the last value is an empty string (from "Clear All") and returns the string "0".

  • CalcResults
    • C#:
          private void CalcResults()
          {
              double d;
              if (LastOper == Operation.None)
                  return;
              
              d = Calc(LastOper);
              Display = d.ToString();
              
              UpdateDisplay();
          }
          
    • Notes:

      First, a variable, "d", is declared to save the numerical result of the upcoming calculation. If there is no operation to be performed (as is the case after "+/-" is completed), this method just returns and nothing happens. However, if there is an operation to be performed it calls the "Calc" method to do the dirty work. The result of the operation is sent to the display and the display is updated. Again, I am very aware of object oriented programming at work (I guess it's actually more of an example of procedural programming since all these functions/methods are in the same class).

  • Calc
    • C#:
          private double Calc(Operation LastOper)
          {
              double d = 0.0;
              
              try {
                  switch (LastOper)
                  {
                      ...
                      case Operation.Negate:
                      d = Convert.ToDouble(LastValue) * (-1.0F);
                      break;
                  }
              }
              catch {
                  d = 0;
                  Window parent = (Window)MyPanel.Parent;
                  //Paper.AddResult("Error");
                  MessageBox.Show(parent, "Operation cannot be perfomed", parent.Title);
              }
              
              return d;
          }
          
    • Notes:

      Again, I am only highlighting "+/-". The variable "d" is used store the result of the calculation and then we come to a Try/Catch block for exception handling. Many of the operations check the calculation to make sure there's nothing strange like infinities and then throw an exception which will be caught by the "catch" block. I will look at those next time. Notice the Operation.Negate case won't forcefully throw an exception since it's only multiplying the last value displayed by negative one and the last value should have already been checked for sanity. However, if something were to go wrong we can see that the error will be noted by adding it to "Paper" (not relevant now) and a standard message box will be rendered.

When you click on a button, a lot of things happen. I think it's really good that I chose to continue the Calculator Demo. In this part, I got to understand enums, try/except, and a bit more about properties and modular code. I will go through the other operations in the next part though I won't have to be nearly as detailed as here.

[update Jan 15, 2010]

I just noticed, I never actually talked about the animation though I did actually complete them at the time.

I will simply post my code with comments (these go in "InitializeThis"):

    ...
    //<Storyboard x:Key="playStoryboard">
    Storyboard playStoryboard = new Storyboard();
    this.RegisterName("playStoryboard", playStoryboard);
    //  <DoubleAnimation From="50"  To="40" Duration="0:0:0.25" RepeatBehavior="1x" AutoReverse="True" 
    //      Storyboard.TargetName="TB" Storyboard.TargetProperty="(Ellipse.Height)"/>
    DoubleAnimation TBHeightAnimation = new DoubleAnimation(50.0, 40.0, new Duration(new TimeSpan(0, 0, 0, 0, 250))); //guess
    TBHeightAnimation.RepeatBehavior = new RepeatBehavior(1.0); //1x gives the error "cannot implicitly convert int to RepeatBehaviour"
    TBHeightAnimation.AutoReverse = true;
    Storyboard.SetTargetName(TBHeightAnimation, "TB");
    Storyboard.SetTargetProperty(TBHeightAnimation, new PropertyPath(Ellipse.HeightProperty));
    playStoryboard.Children.Add(TBHeightAnimation);
    
    //  <DoubleAnimation From="50"  To="44" Duration="0:0:0.25" RepeatBehavior="1x" AutoReverse="True" 
    //      Storyboard.TargetName="TB" Storyboard.TargetProperty="(Ellipse.Width)"/>
    DoubleAnimation TBWidthAnimation = new DoubleAnimation(50.0, 44.0, new Duration(new TimeSpan(0, 0, 0, 0, 250)));
    TBWidthAnimation.RepeatBehavior = new RepeatBehavior(1.0);
    TBWidthAnimation.AutoReverse = true;
    Storyboard.SetTargetName(TBWidthAnimation, "TB");
    Storyboard.SetTargetProperty(TBWidthAnimation, new PropertyPath(Ellipse.WidthProperty));
    playStoryboard.Children.Add(TBWidthAnimation);
    //</Storyboard>
    MyGrid.Resources.Add("playStoryboard", playStoryboard);
    /* The animations make the ellipses become circles... (including Microsoft's) */
    ...
    

Near the definition of setter4

    this.RegisterName("TB", TB);
    ...
    //<ControlTemplate.Triggers>
    //    <Trigger Property="IsMouseOver" Value="true">
    Trigger IMO = new Trigger();
    IMO.Property = Button.IsMouseOverProperty; //guess
    IMO.Value = true;
    //        <Setter TargetName="TB" Property="Ellipse.Fill" Value="Lightblue" />
    Setter IMOSetter = new Setter(Ellipse.FillProperty, Brushes.LightBlue, "TB");
    IMO.Setters.Add(IMOSetter);
    //    </Trigger>
    ct.Triggers.Add(IMO);
    
    //    <Trigger Property="IsPressed" Value="true">
    Trigger IP = new Trigger();
    IP.Property = Button.IsPressedProperty;
    IP.Value = true;
    //        <Setter TargetName="TB" Property="Ellipse.Fill" Value="Blue" />
    Setter IPSetter = new Setter(Ellipse.FillProperty, Brushes.Blue, "TB");
    IP.Setters.Add(IPSetter);
    //    </Trigger>
    ct.Triggers.Add(IP);
    
    //    <EventTrigger RoutedEvent="ButtonBase.Click">
    //http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.buttonbase.aspx
    EventTrigger BBC = new EventTrigger(System.Windows.Controls.Primitives.ButtonBase.ClickEvent);
    //        <EventTrigger.Actions>
    //            <BeginStoryboard Name="playStoryboard" Storyboard="{StaticResource playStoryboard}"/>
    BeginStoryboard BsPs = new BeginStoryboard();
    BsPs.Storyboard = (Storyboard)MyGrid.Resources["playStoryboard"];
    BsPs.Name = "playStoryboard";
    //        </EventTrigger.Actions>
    BBC.Actions.Add(BsPs);
    //    </EventTrigger>
    ct.Triggers.Add(BBC);
    //</ControlTemplate.Triggers>
    

[/update]

0 comments:

Post a Comment