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.

549 lines
16 KiB

/************************************************************************
Extended WPF Toolkit
Copyright (C) 2010-2012 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
This program can be provided to you by Xceed Software Inc. under a
proprietary commercial license agreement for use in non-Open Source
projects. The commercial version of Extended WPF Toolkit also includes
priority technical support, commercial updates, and many additional
useful WPF controls if you license Xceed Business Suite for WPF.
Visit http://xceed.com and follow @datagrid on Twitter.
**********************************************************************/
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 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
#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 )
{
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
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 );
_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 );
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
}
}