All the controls missing in WPF. Over 1 million downloads.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

494 lines
17 KiB

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using Microsoft.Windows.Controls.Core.Utilities;
using System.Collections.Generic;
namespace Microsoft.Windows.Controls
{
public class Calculator : Control
{
#region Members
private ContentControl _buttonPanel;
private bool _showNewNumber = true;
private decimal _previousValue;
private Operation _lastOperation = Operation.None;
private CalculatorButtonType _lastButtonPressed;
private readonly Dictionary<Button, DispatcherTimer> _timers = new Dictionary<Button, DispatcherTimer>();
#endregion //Members
#region Enumerations
public enum CalculatorButtonType
{
Add,
Back,
Cancel,
Clear,
Decimal,
Divide,
Eight,
Equal,
Five,
Four,
Fraction,
MAdd,
MC,
MR,
MS,
MSub,
Multiply,
Negate,
Nine,
None,
One,
Percent,
Seven,
Six,
Sqrt,
Subtract,
Three,
Two,
Zero
}
public enum Operation
{
Add,
Subtract,
Divide,
Multiply,
Percent,
Sqrt,
Fraction,
None,
Clear,
Negate
}
#endregion //Enumerations
#region Properties
public ICommand CalculaterButtonClickCommand { get; private set; }
#region CalculatorButtonType
public static readonly DependencyProperty CalculatorButtonTypeProperty = DependencyProperty.RegisterAttached("CalculatorButtonType", typeof(CalculatorButtonType), typeof(Calculator), new UIPropertyMetadata(CalculatorButtonType.None, OnCalculatorButtonTypeChanged));
public static CalculatorButtonType GetCalculatorButtonType(DependencyObject target)
{
return (CalculatorButtonType)target.GetValue(CalculatorButtonTypeProperty);
}
public static void SetCalculatorButtonType(DependencyObject target, CalculatorButtonType value)
{
target.SetValue(CalculatorButtonTypeProperty, value);
}
private static void OnCalculatorButtonTypeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
OnCalculatorButtonTypeChanged(o, (CalculatorButtonType)e.OldValue, (CalculatorButtonType)e.NewValue);
}
private static void OnCalculatorButtonTypeChanged(DependencyObject o, CalculatorButtonType oldValue, CalculatorButtonType newValue)
{
Button button = o as Button;
button.CommandParameter = newValue;
button.Content = CalculatorUtilities.GetCalculatorButtonContent(newValue);
}
#endregion //CalculatorButtonType
#region DisplayText
public static readonly DependencyProperty DisplayTextProperty = DependencyProperty.Register("DisplayText", typeof(string), typeof(Calculator), new UIPropertyMetadata("0", OnDisplayTextChanged));
public string DisplayText
{
get { return (string)GetValue(DisplayTextProperty); }
set { SetValue(DisplayTextProperty, value); }
}
private static void OnDisplayTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
Calculator calculator = o as Calculator;
if (calculator != null)
calculator.OnDisplayTextChanged((string)e.OldValue, (string)e.NewValue);
}
protected virtual void OnDisplayTextChanged(string oldValue, string newValue)
{
// TODO: Add your property changed side-effects. Descendants can override as well.
}
#endregion //DisplayText
#region Memory
public static readonly DependencyProperty MemoryProperty = DependencyProperty.Register("Memory", typeof(decimal), typeof(Calculator), new UIPropertyMetadata(default(decimal)));
public decimal Memory
{
get { return (decimal)GetValue(MemoryProperty); }
set { SetValue(MemoryProperty, value); }
}
#endregion //Memory
#region Precision
public static readonly DependencyProperty PrecisionProperty = DependencyProperty.Register("Precision", typeof(int), typeof(Calculator), new UIPropertyMetadata(6));
public int Precision
{
get { return (int)GetValue(PrecisionProperty); }
set { SetValue(PrecisionProperty, value); }
}
#endregion //Precision
#region Value
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(decimal?), typeof(Calculator), new FrameworkPropertyMetadata(default(decimal), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));
public decimal? Value
{
get { return (decimal?)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
Calculator calculator = o as Calculator;
if (calculator != null)
calculator.OnValueChanged((decimal?)e.OldValue, (decimal?)e.NewValue);
}
protected virtual void OnValueChanged(decimal? oldValue, decimal? newValue)
{
if (newValue.HasValue)
DisplayText = newValue.ToString();
else
DisplayText = "0";
RoutedPropertyChangedEventArgs<object> args = new RoutedPropertyChangedEventArgs<object>(oldValue, newValue);
args.RoutedEvent = ValueChangedEvent;
RaiseEvent(args);
}
#endregion //Value
#endregion //Properties
#region Constructors
static Calculator()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Calculator), new FrameworkPropertyMetadata(typeof(Calculator)));
}
public Calculator()
{
CommandBindings.Add(new CommandBinding(CalculatorCommands.CalculatorButtonClick, ExecuteCalculatorButtonClick));
AddHandler(MouseDownEvent, new MouseButtonEventHandler(Calculator_OnMouseDown), true);
}
#endregion //Constructors
#region Base Class Overrides
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_buttonPanel = (ContentControl)GetTemplateChild("PART_CalculatorButtonPanel");
}
protected override void OnTextInput(TextCompositionEventArgs e)
{
var buttonType = CalculatorUtilities.GetCalculatorButtonTypeFromText(e.Text);
if (buttonType != CalculatorButtonType.None)
{
SimulateCalculatorButtonClick(buttonType);
ProcessCalculatorButton(buttonType);
}
}
#endregion //Base Class Overrides
#region Event Handlers
private void Calculator_OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (!IsFocused)
{
Focus();
e.Handled = true;
}
}
void Timer_Tick(object sender, EventArgs e)
{
DispatcherTimer timer = (DispatcherTimer)sender;
timer.Stop();
timer.Tick -= Timer_Tick;
if (_timers.ContainsValue(timer))
{
var button = _timers.Where(x => x.Value == timer).Select(x => x.Key).FirstOrDefault();
if (button != null)
{
VisualStateManager.GoToState(button, button.IsMouseOver ? "MouseOver" : "Normal", true);
_timers.Remove(button);
}
}
}
#endregion //Event Handlers
#region Methods
private void Calculate()
{
if (_lastOperation == Operation.None)
return;
try
{
Value = Decimal.Round(CalculateValue(_lastOperation), Precision);
}
catch
{
Value = null;
DisplayText = "ERROR";
}
}
private void Calculate(Operation newOperation)
{
if (!_showNewNumber)
Calculate();
_lastOperation = newOperation;
}
private void Calculate(Operation currentOperation, Operation newOperation)
{
_lastOperation = currentOperation;
Calculate();
_lastOperation = newOperation;
}
private decimal CalculateValue(Operation operation)
{
decimal newValue = decimal.Zero;
decimal currentValue = CalculatorUtilities.ParseDecimal(DisplayText);
switch (operation)
{
case Operation.Add:
newValue = CalculatorUtilities.Add(_previousValue, currentValue);
break;
case Operation.Subtract:
newValue = CalculatorUtilities.Subtract(_previousValue, currentValue);
break;
case Operation.Multiply:
newValue = CalculatorUtilities.Multiply(_previousValue, currentValue);
break;
case Operation.Divide:
newValue = CalculatorUtilities.Divide(_previousValue, currentValue);
break;
//case Operation.Percent:
// newValue = CalculatorUtilities.Percent(_previousValue, currentValue);
// break;
case Operation.Sqrt:
newValue = CalculatorUtilities.SquareRoot(currentValue);
break;
case Operation.Fraction:
newValue = CalculatorUtilities.Fraction(currentValue);
break;
case Operation.Negate:
newValue = CalculatorUtilities.Negate(currentValue);
break;
default:
newValue = decimal.Zero;
break;
}
return newValue;
}
void ProcessBackKey()
{
string displayText;
if (DisplayText.Length > 1 && !(DisplayText.Length == 2 && DisplayText[0] == '-'))
{
displayText = DisplayText.Remove(DisplayText.Length - 1, 1);
}
else
{
displayText = "0";
_showNewNumber = true;
}
DisplayText = displayText;
}
private void ProcessCalculatorButton(CalculatorButtonType buttonType)
{
if (CalculatorUtilities.IsDigit(buttonType))
ProcessDigitKey(buttonType);
else if ((CalculatorUtilities.IsMemory(buttonType)))
ProcessMemoryKey(buttonType);
else
ProcessOperationKey(buttonType);
_lastButtonPressed = buttonType;
}
private void ProcessDigitKey(CalculatorButtonType buttonType)
{
if (_showNewNumber)
DisplayText = CalculatorUtilities.GetCalculatorButtonContent(buttonType);
else
DisplayText += CalculatorUtilities.GetCalculatorButtonContent(buttonType);
_showNewNumber = false;
}
private void ProcessMemoryKey(Calculator.CalculatorButtonType buttonType)
{
decimal currentValue = CalculatorUtilities.ParseDecimal(DisplayText);
switch (buttonType)
{
case Calculator.CalculatorButtonType.MAdd:
Memory += currentValue;
break;
case Calculator.CalculatorButtonType.MC:
Memory = decimal.Zero;
break;
case Calculator.CalculatorButtonType.MR:
DisplayText = Memory.ToString();
break;
case Calculator.CalculatorButtonType.MS:
Memory = currentValue;
break;
case Calculator.CalculatorButtonType.MSub:
Memory -= currentValue;
break;
default:
break;
}
_showNewNumber = true;
}
private void ProcessOperationKey(CalculatorButtonType buttonType)
{
switch (buttonType)
{
case CalculatorButtonType.Add:
Calculate(Operation.Add);
break;
case CalculatorButtonType.Subtract:
Calculate(Operation.Subtract);
break;
case CalculatorButtonType.Multiply:
Calculate(Operation.Multiply);
break;
case CalculatorButtonType.Divide:
Calculate(Operation.Divide);
break;
case CalculatorButtonType.Percent:
if (_lastOperation != Operation.None)
{
decimal currentValue = CalculatorUtilities.ParseDecimal(DisplayText);
decimal newValue = CalculatorUtilities.Percent(_previousValue, currentValue);
DisplayText = newValue.ToString();
}
else
{
DisplayText = "0";
_showNewNumber = true;
}
return;
case CalculatorButtonType.Sqrt:
Calculate(Operation.Sqrt, Operation.None);
break;
case CalculatorButtonType.Fraction:
Calculate(Operation.Fraction, Operation.None);
break;
case CalculatorButtonType.Negate:
Calculate(Operation.Negate, Operation.None);
break;
case CalculatorButtonType.Equal:
Calculate(Operation.None);
break;
case CalculatorButtonType.Clear:
Calculate(Operation.Clear, Operation.None);
DisplayText = Value.ToString();
break;
case CalculatorButtonType.Cancel:
DisplayText = _previousValue.ToString();
_lastOperation = Operation.None;
_showNewNumber = true;
return;
case CalculatorButtonType.Back:
ProcessBackKey();
return;
default:
break;
}
Decimal.TryParse(DisplayText, out _previousValue);
_showNewNumber = true;
}
private void SimulateCalculatorButtonClick(CalculatorButtonType buttonType)
{
var button = CalculatorUtilities.FindButtonByCalculatorButtonType(_buttonPanel, buttonType);
if (button != null)
{
VisualStateManager.GoToState(button, "Pressed", true);
DispatcherTimer timer;
if (_timers.ContainsKey(button))
{
timer = _timers[button];
timer.Stop();
}
else
{
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += Timer_Tick;
_timers.Add(button, timer);
}
timer.Start();
}
}
#endregion //Methods
#region Events
//Due to a bug in Visual Studio, you cannot create event handlers for nullable args in XAML, so I have to use object instead.
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<object>), typeof(Calculator));
public event RoutedPropertyChangedEventHandler<object> ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
#endregion //Events
#region Commands
private void ExecuteCalculatorButtonClick(object sender, ExecutedRoutedEventArgs e)
{
var buttonType = (CalculatorButtonType)e.Parameter;
ProcessCalculatorButton(buttonType);
}
#endregion //Commands
}
}