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.

578 lines
17 KiB

/*************************************************************************************
Extended WPF Toolkit
Copyright (C) 2007-2013 Xceed Software Inc.
This program is provided to you under the terms of the Microsoft Public
License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license
For more features, controls, and fast professional support,
pick up the Plus Edition at http://xceed.com/wpf_toolkit
Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids
***********************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using Xceed.Wpf.Toolkit.Core.Utilities;
namespace Xceed.Wpf.Toolkit
{
[TemplatePart( Name = PART_CalculatorButtonPanel, Type = typeof( ContentControl ) )]
public class Calculator : Control
{
private const string PART_CalculatorButtonPanel = "PART_CalculatorButtonPanel";
#region Members
private ContentControl _buttonPanel;
private bool _showNewNumber = true;
private decimal _previousValue;
private Operation _lastOperation = Operation.None;
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
#region CalculatorButtonPanelTemplate
public static readonly DependencyProperty CalculatorButtonPanelTemplateProperty = DependencyProperty.Register( "CalculatorButtonPanelTemplate"
, typeof( ControlTemplate ), typeof( Calculator ), new UIPropertyMetadata( null ) );
public ControlTemplate CalculatorButtonPanelTemplate
{
get
{
return (ControlTemplate)GetValue( CalculatorButtonPanelTemplateProperty );
}
set
{
SetValue( CalculatorButtonPanelTemplateProperty, value );
}
}
#endregion //CalculatorButtonPanelTemplate
#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;
if( button.Content == null )
{
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 )
{
SetDisplayText( newValue );
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 = GetTemplateChild( PART_CalculatorButtonPanel ) as ContentControl;
}
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
internal void InitializeToValue(decimal? value)
{
_previousValue = 0;
_lastOperation = Operation.None;
_showNewNumber = true;
Value = value;
// Since the display text may be out of sync
// with "Value", this call will force the
// text update if Value was already equal to
// the value parameter.
this.SetDisplayText( value );
}
private void Calculate()
{
if( _lastOperation == Operation.None )
return;
try
{
Value = Decimal.Round( CalculateValue( _lastOperation ), Precision );
SetDisplayText( Value ); //Set DisplayText even when Value doesn't change
}
catch
{
Value = null;
DisplayText = "ERROR";
}
}
private void SetDisplayText( decimal? newValue )
{
if( newValue.HasValue && ( newValue.Value != 0 ) )
DisplayText = newValue.ToString();
else
DisplayText = "0";
}
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 );
}
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 );
_showNewNumber = true;
switch( buttonType )
{
case Calculator.CalculatorButtonType.MAdd:
Memory += currentValue;
break;
case Calculator.CalculatorButtonType.MC:
Memory = decimal.Zero;
break;
case Calculator.CalculatorButtonType.MR:
DisplayText = Memory.ToString();
_showNewNumber = false;
break;
case Calculator.CalculatorButtonType.MS:
Memory = currentValue;
break;
case Calculator.CalculatorButtonType.MSub:
Memory -= currentValue;
break;
default:
break;
}
}
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 );
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
}
}