WPF Custom TextBox with Decimal Formatting - wpf

I am new to WPF.
I have a requirement that I need to develop a custom textbox control which should support the functionality like:
Should accept only decimal values.
Should round off to 3 decimal places when assigned a value through code or by the user.
Should show the full value(without formatting) on focus.
Eg:
If 2.21457 is assigned to textbox(by code or by user), it should display 2.215. When user clicks in it to edit it, it must show the full value 2.21457.
After the user edits the value to 5.42235 and tabs out, it should again round off to 5.422.
Tried it without success. So need some help on it.
Thanks in advance for the help.
Thanks

I have written a custom control which will have dependency property called ActualText. Bind your value into that ActualText property and manipulated the Text property of the textbox during the gotfocus and lostfocus event. Also validated for decimal number in the PreviewTextInput event. refer the below code.
class TextBoxEx:TextBox
{
public string ActualText
{
get { return (string)GetValue(ActualTextProperty); }
set { SetValue(ActualTextProperty, value); }
}
// Using a DependencyProperty as the backing store for ActualText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ActualTextProperty =
DependencyProperty.Register("ActualText", typeof(string), typeof(TextBoxEx), new PropertyMetadata(string.Empty, OnActualTextChanged));
private static void OnActualTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox tx = d as TextBox;
tx.Text = (string)e.NewValue;
string str = tx.Text;
double dbl = Convert.ToDouble(str);
str = string.Format("{0:0.###}", dbl);
tx.Text = str;
}
public TextBoxEx()
{
this.GotFocus += TextBoxEx_GotFocus;
this.LostFocus += TextBoxEx_LostFocus;
this.PreviewTextInput += TextBoxEx_PreviewTextInput;
}
void TextBoxEx_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
decimal d;
if(!decimal.TryParse(e.Text,out d))
{
e.Handled = true;
}
}
void TextBoxEx_LostFocus(object sender, System.Windows.RoutedEventArgs e)
{
ConvertText();
}
void TextBoxEx_GotFocus(object sender, System.Windows.RoutedEventArgs e)
{
this.Text = ActualText;
}
private void ConvertText()
{
string str = this.Text;
ActualText = str;
double dbl = Convert.ToDouble(str);
str = string.Format("{0:0.###}", dbl);
this.Text = str;
}
}

Related

Extended WPF Toolkit DoubleUpDown

I've added an Extended WPF Toolkit DoubleUpDown control.
The behaviour is if you type 5.35 it is fine.
Then say you type 4.3errortext7 and tab it reverts back to 5.35 (as the 4.3errortext7 is not a valid number).
However I'd like it just to go to 4.37 in that case (and ignore the invalid characters.
Is there an elegant way to get my required behaviour?
The best I was able to come up with is to use the PreviewTextInput Event and check for valid input, unfortunatly it will allow still allow a space between numbers, but will prevent all other text from being input.
private void doubleUpDown1_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (!(char.IsNumber(e.Text[0]) || e.Text== "." ))
{
e.Handled = true;
}
}
Maybe a bit late but I had the same problem yesterday and I also did not want to register a handler every time I use the control. I mixed the solution by Mark Hall with an Attached Behavior (inspired by this post: http://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF):
public static class DoubleUpDownBehavior
{
public static readonly DependencyProperty RestrictInputProperty =
DependencyProperty.RegisterAttached("RestrictInput", typeof(bool),
typeof(DoubleUpDownBehavior),
new UIPropertyMetadata(false, OnRestrictInputChanged));
public static bool GetRestrictInput(DoubleUpDown ctrl)
{
return (bool)ctrl.GetValue(RestrictInputProperty);
}
public static void SetRestrictInput(DoubleUpDown ctrl, bool value)
{
ctrl.SetValue(RestrictInputProperty, value);
}
private static void OnRestrictInputChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
DoubleUpDown item = depObj as DoubleUpDown;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.PreviewTextInput += OnPreviewTextInput;
else
item.PreviewTextInput -= OnPreviewTextInput;
}
private static void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (!(char.IsNumber(e.Text[0]) ||
e.Text == CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator))
{
e.Handled = true;
}
}
}
Then you can simply set the default style for DoubleUpDown like this:
<Style TargetType="xctk:DoubleUpDown">
<Setter Property="behaviors:DoubleUpDownBehavior.RestrictInput" Value="True" />
</Style>
In my case, it was much better to use regex.
private void UpDownBox_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
var upDownBox = (sender as DoubleUpDown);
TextBox textBoxInTemplate = (TextBox)upDownBox.Template.FindName("PART_TextBox", upDownBox);
Regex regex = new Regex("^[.][0-9]+$|^[0-9]*[.]{0,1}[0-9]*$");
e.Handled = !regex.IsMatch(upDownBox.Text.Insert((textBoxInTemplate).SelectionStart, e.Text));
}

RibbonApplicationMenu: getting rid of the AuxiliaryPane

It so happened that the application I'm working on doesn't operate on documents, so there's no need in displaying the recently opened documents list in the application menu.
But - annoyingly - there are no properties readily available in the RibbonApplicationMenu class to hide the unused AuxiliaryPane (for which, curiously, the property does exist, but is marked as "internal").
Of course, I can just leave it there - but that's... untidy.
So, here's the solution I came up with.
Hope it will be helpful for anyone else :-)
The general idea is to subclass the RibbonApplicationMenu, find the template child corresponding to the menu's Popup, and overrule its Width (after a number of frustrating experiments it became evident that doing that neither for PART_AuxiliaryPaneContentPresenter nor for PART_FooterPaneContentPresenter - nor for the both - could achieve anything).
Well, without further ado, here's the code:
public class SlimRibbonApplicationMenu : RibbonApplicationMenu
{
private const double DefaultPopupWidth = 180;
public double PopupWidth
{
get { return (double)GetValue(PopupWidthProperty); }
set { SetValue(PopupWidthProperty, value); }
}
public static readonly DependencyProperty PopupWidthProperty =
DependencyProperty.Register("PopupWidth", typeof(double),
typeof(SlimRibbonApplicationMenu), new UIPropertyMetadata(DefaultPopupWidth));
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.DropDownOpened +=
new System.EventHandler(SlimRibbonApplicationMenu_DropDownOpened);
}
void SlimRibbonApplicationMenu_DropDownOpened(object sender, System.EventArgs e)
{
DependencyObject popupObj = base.GetTemplateChild("PART_Popup");
Popup popupPanel = (Popup)popupObj;
popupPanel.Width = (double)GetValue(PopupWidthProperty);
}
}
As a side note, I tried to find any way to resolve the desired width based on the max width of the ApplicationMenu's Items (rather than setting it explicitly through the DependencyProperty in XAML) - but to no avail.
Given my despise to "magic numbers", any suggestion on that will be deeply appreciated.
I know this has been a while, but I've got another solution to this. This one does not provide the Popup width property, instead a ShowAuxilaryPanel boolean. It then goes to Bind the width of the Popup, to the width of the menu item area of the menu.
public class SlimRibbonApplicationMenu : RibbonApplicationMenu
{
public bool ShowAuxilaryPanel
{
get { return (bool)GetValue(ShowAuxilaryPanelProperty); }
set { SetValue(ShowAuxilaryPanelProperty, value); }
}
public static readonly DependencyProperty ShowAuxilaryPanelProperty =
DependencyProperty.Register("ShowAuxilaryPanel", typeof(bool),
typeof(SlimRibbonApplicationMenu), new UIPropertyMetadata(true));
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.DropDownOpened += SlimRibbonApplicationMenu_DropDownOpened;
}
void SlimRibbonApplicationMenu_DropDownOpened(object sender, EventArgs e)
{
DependencyObject popupObj = base.GetTemplateChild("PART_Popup");
Popup panel = (Popup)popupObj;
var exp = panel.GetBindingExpression(Popup.WidthProperty);
if (!this.ShowAuxilaryPanel && exp == null)
{
DependencyObject panelArea = base.GetTemplateChild("PART_SubMenuScrollViewer");
var panelBinding = new Binding("ActualWidth")
{
Source = panelArea,
Mode = BindingMode.OneWay
};
panel.SetBinding(Popup.WidthProperty, panelBinding);
}
else if (this.ShowAuxilaryPanel && exp != null)
{
BindingOperations.ClearBinding(panel, Popup.WidthProperty);
}
}
}
worked for me
<telerik:ApplicationMenu RightPaneVisibility="Collapsed" >

Keypad decimal separator on a Wpf TextBox, how to?

I have a Wpf application with some textbox for decimal input.
I would that when I press "dot" key (.) on numeric keypad of pc keyboard it send the correct decimal separator.
For example, on Italian language the decimal separator is "comma" (,)...Is possible set the "dot" key to send the "comma" character when pressed?
Quick and dirty:
private void NumericTextBox_KeyDown(object sender, KeyEventArgs e) {
if (e.Key == Key.Decimal) {
var txb = sender as TextBox;
int caretPos=txb.CaretIndex;
txb.Text = txb.Text.Insert(txb.CaretIndex, System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator);
txb.CaretIndex = caretPos + 1;
e.Handled = true;
}
}
Although you may set the default converter locale in WPF as suggested by Mamta Dalal it is not enough to convert the "decimal" key press to the correct string. This code will display the correct currency symbol and date/time format on data-bound controls
//Will set up correct string formats for data-bound controls,
// but will not replace numpad decimal key press
private void Application_Startup(object sender, StartupEventArgs e)
{
//Among other settings, this code may be used
CultureInfo ci = CultureInfo.CurrentUICulture;
try
{
//Override the default culture with something from app settings
ci = new CultureInfo([insert your preferred settings retrieval method here]);
}
catch { }
Thread.CurrentThread.CurrentCulture = ci;
Thread.CurrentThread.CurrentUICulture = ci;
//Here is the important part for databinding default converters
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(ci.IetfLanguageTag)));
//Other initialization things
}
I found that handling the previewKeyDown event window-wide is a little cleaner than textbox-specific (it would be better if it could be applied application-wide).
public partial class MainWindow : Window
{
public MainWindow()
{
//Among other code
if (CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator != ".")
{
//Handler attach - will not be done if not needed
PreviewKeyDown += new KeyEventHandler(MainWindow_PreviewKeyDown);
}
}
void MainWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Decimal)
{
e.Handled = true;
if (CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator.Length > 0)
{
Keyboard.FocusedElement.RaiseEvent(
new TextCompositionEventArgs(
InputManager.Current.PrimaryKeyboardDevice,
new TextComposition(InputManager.Current,
Keyboard.FocusedElement,
CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator)
) { RoutedEvent = TextCompositionManager.TextInputEvent});
}
}
}
}
If anybody could come up with a way to set it application-wide...

How to animate "typing" text in SketchFlow?

In Microsoft's Expression Blend 3 SketchFlow application.
How would you go about animating the typing of text, ideally in staged character by character fashion. As if the user is typing it.
An associated flashing cursor would make it perfect, but that's far into the realm of "nice to have".
The keyframe animation system, does not allow you to manipulate the
Common Property > Text
field so therefore it doesn't persist as a recorded change in that keyframe of animation.
I'm looking for either editor steps (using some kind of other control) or even XAML code...
<VisualState>
<StoryBoard>
<DoubleAnimationUsingKeyFrame ... >
After blogging about this with a solution involving a wipe animation of a rectangle over a text block, a response blog post with a more advanced solution of using a custom behavior attached to a text block was created.
Creating a 'TypeOnAction' behavior and adding to a TextBlock, will give the desired effect of character by character display, with a customizable appearance rate. Get the full code sample here.
public class TypeOnAction : TriggerAction<TextBlock>
{
DispatcherTimer timer;
int len = 1;
public TypeOnAction()
{
timer = new DispatcherTimer();
}
protected override void Invoke(object o)
{
if (AssociatedObject == null)
return;
AssociatedObject.Text = "";
timer.Interval = TimeSpan.FromSeconds(IntervalInSeconds);
timer.Tick += new EventHandler(timer_Tick);
len = 1;
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{
if (len > 0 && len <= TypeOnText.Length)
{
AssociatedObject.Text = TypeOnText.Substring(0, len);
len++;
timer.Start();
}
else
timer.Stop();
}
public string TypeOnText
{
get { return (string)GetValue(TypeOnTextProperty); }
set { SetValue(TypeOnTextProperty, value); }
}
public static readonly DependencyProperty TypeOnTextProperty =
DependencyProperty.Register("TypeOnText", typeof(string), typeof(TypeOnAction), new PropertyMetadata(""));
public double IntervalInSeconds
{
get { return (double)GetValue(IntervalInSecondsProperty); }
set { SetValue(IntervalInSecondsProperty, value); }
}
public static readonly DependencyProperty IntervalInSecondsProperty =
DependencyProperty.Register("IntervalInSeconds", typeof(double), typeof(TypeOnAction), new PropertyMetadata(0.35));
}

Silverlight Convert PlatformKeyCode to Character Code

Question:
Is there a good way in Silverlight to intercept undesirable characters from being entered into a textbox?
Background:
I have a textbox which allows a user to enter a filename. I would like to exclude invalid file characters from being entered into the textbox. A few of these characters are:
'?'
'\'
'<'
'>'
Although the Silverlight TextBox class does not support a KeyPress event, it does have a KeyDown and a KeyUp event that can be used to retrieve character information when a key is entered into the textbox. It exposes these as a member of the Key enumeration or it can return an int for the PlatformKeyCode.
Of course the range of keys is larger/different from the range of characters - "F keys" are an example of this. However the presence of something like a KeyPress event in Windows Forms is indicative of the usefulness of being able to extract specific character information.
To do a proof of concept that things could work I hardcoded the PlatformKeyCode values for the undesired characters for my platform into the event handler and everything worked... but of course this is just my platform. I need to make sure this implementation is platform agnostic. Here is the code to demonstrate how I would like it to work:
private void theText_KeyDown(object sender, KeyEventArgs e)
{
int[] illegals = { 191, 188, 190, 220, 186, 222, 191, 56, 186};
if (illegals.Any(i => i == e.PlatformKeyCode)) e.Handled = true;
}
Both responses (in comments) from Henrik and Johannes contain the best answer for the scenario. Rather than thinking like a Windows Forms programmer and capturing the specific key event, the canonical approach in Silverlight is to use the TextChanged event to remove undesirable characters from a TextBox. A similar question on allowing only numeric input to a TextBox is what prompted the solution that was used.
The following code samples the approach I took after reading the comments and it works quite well to remove characters inappropriate for entry:
private void fileNameTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
string illegalChars = #"?<>:""\/*|";
fileNameTextBox.Text = String.Join("", fileNameTextBox.Text.Split(illegalChars.ToCharArray()));
fileNameTextBox.SelectionStart = fileNameTextBox.Text.Length;
}
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class FilterTextBoxBehavior : Behavior<TextBox>
{
public readonly static DependencyProperty AllowAlphaCharactersProperty = DependencyProperty.Register("AllowAlphaCharacters", typeof(bool), typeof(FilterTextBoxBehavior), new PropertyMetadata(true));
public bool AllowAlphaCharacters
{
get { return (bool)GetValue(AllowAlphaCharactersProperty); }
set { SetValue(AllowAlphaCharactersProperty, value); }
}
public readonly static DependencyProperty AllowNumericCharactersProperty = DependencyProperty.Register("AllowNumericCharacters", typeof(bool), typeof(FilterTextBoxBehavior), new PropertyMetadata(true));
public bool AllowNumericCharacters
{
get { return (bool)GetValue(AllowNumericCharactersProperty); }
set { SetValue(AllowNumericCharactersProperty, value); }
}
public readonly static DependencyProperty AllowSpecialCharactersProperty = DependencyProperty.Register("AllowSpecialCharacters", typeof(bool), typeof(FilterTextBoxBehavior), new PropertyMetadata(true));
public bool AllowSpecialCharacters
{
get { return (bool)GetValue(AllowSpecialCharactersProperty); }
set { SetValue(AllowSpecialCharactersProperty, value); }
}
public readonly static DependencyProperty DoNotFilterProperty = DependencyProperty.Register("DoNotFilter", typeof(string), typeof(FilterTextBoxBehavior), new PropertyMetadata(default(string)));
public string DoNotFilter
{
get { return (string)GetValue(DoNotFilterProperty); }
set { SetValue(DoNotFilterProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
if (AssociatedObject == null) { return; }
FilterAssociatedObject();
AssociatedObject.TextChanged += OnTextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject == null) { return; }
FilterAssociatedObject();
AssociatedObject.TextChanged -= OnTextChanged;
}
private void OnTextChanged(object sender, TextChangedEventArgs e) { FilterAssociatedObject(); }
private void FilterAssociatedObject()
{
int cursorLocation = AssociatedObject.SelectionStart;
for (int i = AssociatedObject.Text.Length - 1; i >= 0; i--)
{
char c = AssociatedObject.Text[i];
if (ValidChar(c)) { continue; }
AssociatedObject.Text = AssociatedObject.Text.Remove(i, 1);
cursorLocation--;
}
AssociatedObject.SelectionStart = Math.Min(AssociatedObject.Text.Length, Math.Max(0, cursorLocation));
}
private bool ValidChar(char c)
{
if (!string.IsNullOrEmpty(DoNotFilter) && DoNotFilter.Contains(c)) { return true; }
if (!AllowAlphaCharacters && char.IsLetter(c)) { return false; }
if (!AllowNumericCharacters && char.IsNumber(c)) { return false; }
if (!AllowSpecialCharacters && Regex.IsMatch(c.ToString(), #"[\W|_]")) { return false; }
return true;
}
}

Resources