I am fairly new to Winforms, and I would like to know how to use a MaskedTextBox to ensure that only numerical values(decimals included) are entered by the user.
I have tried the masked feature using "/^[-+]?[0-9]+(.[0-9]+)?$/" and was not successful with that. What I want, even if with an ordinary textbox, is to allow only numerals/decimal values
Why not just use a NumericUpDown? Set your max and min values, number of decimal places, and away you go.
http://msdn.microsoft.com/en-us/library/system.windows.forms.numericupdown%28v=vs.90%29.aspx
Masks for the MaskedTextBox aren't regex patterns; they use their own syntax.
The pattern you want is probably something like "999,990.9999". This requires the user to enter at least one digit, but they can enter any number from 0 to 999,999.9999 and with up to 4 decimals precision, and will automatically insert the thousands separator when needed.
Another thing you can do, similar to what the MaskedTextBox itself does, is override OnKeyPress to reject any character entered that would cause a Regex not to match:
public class RegexTextBox:TextBox
{
[Category("Behavior")]
public string RegexPattern {get;set;}
protected override OnKeyPress(KeyPressEventArgs e)
{
if (!Regex.IsMatch(this.Text + e.KeyChar, RegexPattern)) e.Handled = true;
else base.OnKeyPress(e);
}
}
You have to be careful when defining the pattern, because it will only work if what's been entered so far matches the pattern. However, it will work in your case, if you use a pattern like "[0-9]{1,3}((,[0-9]{1,3})*(\.[0-9]*)?".
I adapted this NumericTextBox control: http://sanity-free.org/forum/viewtopic.php?pid=1205#p1205
If you want it apart of your toolbox, you'd probably create a new control based on a TextBox and paste the code in that link into the code section.
Here's my version of it. I don't know how much I've modified it as I've had it a long time.
NumericTextBox.Designer.cs:
using System.Windows.Forms;
namespace SeaRisenLib2.Controls
{
partial class NumericTextBox : TextBox
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
//this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
}
#endregion
}
}
NumericTextBox.cs
// From: http://sanity-free.org/forum/viewtopic.php?pid=1205#p1205
// modified it slightly
//
/******************************************************/
/* NULLFX FREE SOFTWARE LICENSE */
/******************************************************/
/* NumericTextBox Library */
/* by: Steve Whitley */
/* © 2005 NullFX Software */
/* */
/* NULLFX SOFTWARE DISCLAIMS ALL WARRANTIES, */
/* RESPONSIBILITIES, AND LIABILITIES ASSOCIATED WITH */
/* USE OF THIS CODE IN ANY WAY, SHAPE, OR FORM */
/* REGARDLESS HOW IMPLICIT, EXPLICIT, OR OBSCURE IT */
/* IS. IF THERE IS ANYTHING QUESTIONABLE WITH REGARDS */
/* TO THIS SOFTWARE BREAKING AND YOU GAIN A LOSS OF */
/* ANY NATURE, WE ARE NOT THE RESPONSIBLE PARTY. USE */
/* OF THIS SOFTWARE CREATES ACCEPTANCE OF THESE TERMS */
/* */
/* USE OF THIS CODE MUST RETAIN ALL COPYRIGHT NOTICES */
/* AND LICENSES (MEANING THIS TEXT). */
/* */
/******************************************************/
/* Changed by Carlos Montiers */
/* Decimal separator "." or "," depending on locale */
/* Constructor for numericTextBox whith or without */
/* negative range and number of decimals. */
/* Does not allow the entry of non-numeric character */
/* through alt + ascii code */
/* Permit use of tab key */
/* Version: 24-10-2008 */
/******************************************************/
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Text.RegularExpressions;
namespace SeaRisenLib2.Controls
{
public partial class NumericTextBox : TextBox
{
private const int WM_KEYDOWN = 0x0100;
private const int WM_PASTE = 0x0302;
private int decimalNumbers;
private bool hasNegatives;
private string format;
public delegate void D(NumericTextBox sender);
/// <summary>
/// Fired when Text changes
/// </summary>
public event D Changed = delegate { };
/// <summary>
/// Constructor of a NumericTextBox, with negative number and 2 decimals.
/// </summary>
public NumericTextBox()
: this(2, true)
{
}
/// <summary>
/// Constructor with parameters
/// </summary>
/// <param name="decimalNumbers">number of decimals</param>
/// <param name="hasNegatives">has negatives</param>
public NumericTextBox(int decimalNumbers, bool hasNegatives)
: base()
{
InitializeComponent();
DecimalNumbers = decimalNumbers;
HasNegatives = hasNegatives;
Format = Format;
}
/// <summary>
/// Property of decimalNumbers
/// </summary>
public int DecimalNumbers
{
get
{
return decimalNumbers;
}
set
{
decimalNumbers = value > 0 ? value : 0;
}
}
/// <summary>
/// Bindable Decimal Text
/// </summary>
public decimal DecimalText
{
get {
if(string.IsNullOrEmpty(Text))
return 0;
return Convert.ToDecimal(Text);
}
set { Text = value.ToString(); }
}
/// <summary>
/// The string text value of the numeric text, use DecimalText to get the numeric value.
/// </summary>
public override string Text
{
get { return base.Text; }
set
{
base.Text = value;
Changed(this);
}
}
/// <summary>
/// Property of hasNegatives
/// </summary>
public bool HasNegatives
{
get
{
return hasNegatives;
}
set
{
hasNegatives = value;
}
}
/// <summary>
/// Property of format
/// </summary>
public string Format
{
get
{
return format;
}
set
{
format = "^";
format += HasNegatives ? "(\\" + getNegativeSign() + "?)" : "";
format += "(\\d*)";
if (DecimalNumbers > 0)
{
format += "(\\" + getDecimalSeparator() + "?)";
for (int i = 1; i <= DecimalNumbers; i++)
{
format += "(\\d?)";
}
}
format += "$";
}
}
/// <summary>
/// Method PreProcessMessage
/// </summary>
/// <param name="msg">ref Message</param>
/// <returns>bool</returns>
public override bool PreProcessMessage(ref Message msg)
{
if (msg.Msg == WM_KEYDOWN)
{
Keys keys = (Keys)msg.WParam.ToInt32();
bool numbers = ModifierKeys != Keys.Shift
&& (keys >= Keys.D0 && keys <= Keys.D9
|| (keys >= Keys.NumPad0 && keys <= Keys.NumPad9));
bool dec = ModifierKeys != Keys.Shift
&& (keys == Keys.Decimal
|| keys == Keys.Oemcomma
|| keys == Keys.OemPeriod);
bool negativeSign = (keys == Keys.OemMinus && ModifierKeys != Keys.Shift)
|| keys == Keys.Subtract;
bool home = keys == Keys.Home;
bool end = keys == Keys.End;
bool ctrlZ = keys == Keys.Z && ModifierKeys == Keys.Control;
bool ctrlX = keys == Keys.X && ModifierKeys == Keys.Control;
bool ctrlC = keys == Keys.C && ModifierKeys == Keys.Control;
bool ctrlV = keys == Keys.V && ModifierKeys == Keys.Control;
bool del = keys == Keys.Delete;
bool bksp = keys == Keys.Back;
bool tab = keys == Keys.Tab;
bool arrows = keys == Keys.Up
|| keys == Keys.Down
|| keys == Keys.Left
|| keys == Keys.Right;
if (numbers || del || bksp || arrows || home || end
|| ctrlC || ctrlX || ctrlV || ctrlZ)
{
return false;
}
else
{
if (dec)
{
return DecimalNumbers <= 0;
}
else
{
if (negativeSign)
{
return !HasNegatives;
}
else
{
if (tab)
{
return base.PreProcessMessage(ref msg);
}
else
{
return true;
}
}
}
}
}
else
{
return base.PreProcessMessage(ref msg);
}
}
/// <summary>
/// Method WndProc
/// </summary>
/// <param name="m">ref Message</param>
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_PASTE)
{
IDataObject obj = Clipboard.GetDataObject();
string input = (string)obj.GetData(typeof(string));
string pasteText = getPosibleText(input);
if (!isValidadFormat(pasteText))
{
m.Result = (IntPtr)0;
return;
}
}
base.WndProc(ref m);
}
/// <summary>
/// Method OnKeyPress
/// </summary>
/// <param name="e">KeyPressEventArgs</param>
protected override void OnKeyPress(KeyPressEventArgs e)
{
base.OnKeyPress(e);
string keyInput = e.KeyChar.ToString();
string inputText = getPosibleText(keyInput);
if (Char.IsDigit(e.KeyChar)
|| keyInput.Equals(getDecimalSeparator())
|| keyInput.Equals(getNegativeSign()))
{
e.Handled = !isValidadFormat(inputText);
}
else if (e.KeyChar == '\b'
|| e.KeyChar == '\t'
|| keyInput.Equals(Keys.Delete.ToString())
|| keyInput.Equals(Keys.Home.ToString())
|| keyInput.Equals(Keys.End.ToString()))
{
//Allow backspace, tab, delete, home, end
}
else if (e.KeyChar == 26
|| e.KeyChar == 24
|| e.KeyChar == 3
|| e.KeyChar == 22)
{
// 26 : Allow Ctrl+Z | 24 : Allow Ctrl+X
// 3 : Allow Ctrl+C | 22 : Allow Ctrl+V
}
else
{
//Disallow
e.Handled = true;
}
}
/// <summary>
/// Method OnTextChanged
/// </summary>
/// <param name="e">System.EventArgs</param>
protected override void OnTextChanged(System.EventArgs e)
{
if (getFloatValue() < 0)
{
this.ForeColor = Color.Red;
}
else
{
this.ForeColor = Color.Black;
}
//If the decimal point is preceded by a no number is added zero
if (this.Text.StartsWith(getNegativeSign() + getDecimalSeparator()))
{
this.Text = getNegativeSign() + "0" + this.Text.Substring(1);
this.Select(3, 0);
}
else
{
if (this.Text.StartsWith(getDecimalSeparator()))
{
this.Text = "0" + this.Text;
this.Select(2, 0);
}
}
base.OnTextChanged(e);
}
/// <summary>
/// Method for validate text with format
/// </summary>
/// <param name="text">text</param>
/// <returns>is valid format</returns>
private bool isValidadFormat(string text)
{
return Regex.IsMatch(text, Format);
}
/// <summary>
/// Method for get deciamal separator
/// </summary>
/// <returns>Decimal Separator of current culture</returns>
private string getDecimalSeparator()
{
return System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
}
/// <summary>
/// Method for get negative sign
/// </summary>
/// <returns>Negative Sign of current culture</returns>
private string getNegativeSign()
{
return System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NegativeSign;
}
/// <summary>
/// Method for get posible resulting text for input text
/// </summary>
/// <param name="text">string with input text</param>
/// <returns>posible text</returns>
private string getPosibleText(string text)
{
string rText;
rText = this.Text.Substring(0, SelectionStart);
rText += text;
rText += this.Text.Substring(SelectionStart + SelectionLength);
return rText;
}
/// <summary>
/// Method for get int value of text
/// </summary>
/// <returns>int value</returns>
public int getIntValue()
{
try
{
return (int)getFloatValue();
}
catch
{
return 0;
}
}
/// <summary>
/// Method for get round int value of text
/// </summary>
/// <returns>round int value</returns>
public int getIntRoundValue()
{
try
{
return (int)Math.Round(getFloatValue());
}
catch
{
return 0;
}
}
/// <summary>
/// Method for get float value of text
/// </summary>
/// <returns>float value</returns>
public float getFloatValue()
{
try
{
return float.Parse(this.Text);
}
catch
{
return 0;
}
}
}
}
thanks , working code below.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace System.Windows.Forms {
public class RegexTextBox : TextBox {
[Category("Behavior")]
public string RegexPattern { get; set; }
private bool DelOrBack = false;
protected override void OnKeyDown(KeyEventArgs e) {
base.OnKeyDown(e);
if (e.KeyCode == Keys.Back || e.KeyCode == Keys.Delete) {
DelOrBack = true;
}
}
protected override void OnKeyPress(KeyPressEventArgs e) {
if (!Regex.IsMatch(this.Text + e.KeyChar, RegexPattern) && !DelOrBack) {
e.Handled = true;
}
else {
base.OnKeyPress(e);
}
DelOrBack = false;
}
}
public class NumericTextBox : RegexTextBox {
public NumericTextBox() {
RegexPattern = "^[0-9]+$";
}
}
}
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 11 months ago.
Improve this question
I need a better TextBox to suit my needs (several purposes).
For instance:
Ability to filter characters as numbers;
Ability to work with currencies (add decimals and currency designators);
Ability to limit the number of characters allowed in the TextBox;
Automatically add decimal places;
More logical (missing) features useful for the Control.
I believe everyone (at some point) shares the same opinion:
WinForms TextBox features are somehow limited, specially when using a TextBox in order to work with Numbers, Currencies or IP Addresses (IPv4 in this Situation).
Thanks in advance.
Answering my own question while providing my humble code:
Info (Last Update: 2022.04.02)
About
This is the Default TextBox with Extra Features.
Remarks
I've been working on this control recently and I'm adding more features on the go. The control should be working fine, even though it may require some further development.
I'll try to keep it updated as soon as possible until perfection is achieved.
See "Known Bugs" bellow.
Features:
Filter / Format / Validate the Text Input (Text, Numeric, Currency or IP Address).
Set Currency Designator Symbol.
Set Currency Designator as a Symbol or Abbreviated Designator Name (i.e: EUR).
Set the Currency Designator Symbol Location.
i.e:
Left: Before the Value.
Right: After the Value.
Set Values as Decimal.
Set Decimal Zeros Automatically when Entering a Whole Number.
Limit Maximum Character Input
Bug Fixes
Prevented Clipboard Data to be Set to the Control.
Default TextBox Initial (Default) Value was Impossible to be Set (for Numeric and Currency Text Inputs).
Character Limiter Function was Missing.
Known Bugs
There is an issue with Text (number of chars) Limiter while using Decimals. TextBox Prevents user Text Input Proper Behaviour.
Sometimes not Accepting Paste.
SHIFT + INSERT, on the other hand is allowed.
Code
using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace RG_Custom_Controls.Controls
{
public class RGTextBoxI : TextBox
{
#region <Constructor>
public RGTextBoxI()
{
// -> Set Default Configuration.
ForeColor = Color.Gainsboro;
BackColor = Color.FromArgb(255, 36, 36, 52);
BorderStyle = BorderStyle.FixedSingle;
}
#endregion
#region <Fields>
private const int WM_PASTE = 0x0302; // Used to Validate Clipboar Data.
private string numbers = "0123456789.";
private string allowedChars => numbers;
private string decimalFormat = string.Empty;
#endregion
#region <Custom Properties> : (Char Limiter)
private bool charsLimited = false;
[Category("1. Custom Properties"), DisplayName("1. Chars Limited")]
[Description("Toggle Character Input Limit.")]
[Browsable(true)]
public bool CharsLimited
{
get { return charsLimited; }
set { charsLimited = value; }
}
private int maximumChars = 32;
[Category("1. Custom Properties"), DisplayName("2. Maximum Chars")]
[Description("Limit the Maximum Number of Chars Allowed.")]
[Browsable(true)]
public int MaximumChars
{
get { return maximumChars; }
set { maximumChars = value; }
}
#endregion
#region <Custom Properties> : (Input Mode)
/// <summary> TextBox Text Iput Mode (Normal, Numeric, Currency). </summary>
public enum TextBoxInputType { Default, Numeric, Currency, IPV4 }
private TextBoxInputType inputType = TextBoxInputType.Default;
[Category("1. Custom Properties"), DisplayName("1. Input Mode")]
[Description("Select Control Mode (Normal, Numeric or Currency).")]
[Bindable(true)] /* Required for Enum Types */
[Browsable(true)]
public TextBoxInputType TextBoxType
{
get { return inputType; }
set
{
inputType = value;
Text_SetDefaultValue();
Text_Align();
Invalidate();
}
}
#endregion
#region <Custom Properties> : (Decimals)
private bool useDecimals;
[Category("1. Custom Properties"), DisplayName("2. Use Decimals")]
[Description("Select wether to use Whole Number or a Decimal Number.")]
[Browsable(true)]
public bool UseDecimals
{
get { return useDecimals; }
set { useDecimals = value; }
}
private int decimalPlaces = 2;
[Category("1. Custom Properties"), DisplayName("3. Decimal Places")]
[Description("Select wether to use Whole Number or a Decimal Number.")]
[Browsable(true)]
public int DecimalPlaces
{
get { return decimalPlaces; }
set
{
if (value > 0 & value < 3)
{
decimalPlaces = value;
// Aet Decimal Format
switch (decimalPlaces)
{
case 1: decimalFormat = "0.0"; break;
case 2: decimalFormat = "0.00"; break;
}
}
}
}
#endregion
#region <Custom Properties> : (Curency Designator)
private string currencyDesignator = "€";
[Category("1. Custom Properties"), DisplayName("4. Currency Designator")]
[Description("Set Currency Symbol or Designator.\n\n i.e: €, Eur, Euros")]
[Browsable(true)]
public string CurrencyDesignator
{
get { return currencyDesignator; }
set { currencyDesignator = value; }
}
public enum DesignatorAlignment { Left, Right }
private DesignatorAlignment designatorAlignment = DesignatorAlignment.Right;
[Category("1. Custom Properties"), DisplayName("5. Designator Location")]
[Description("Select Currency Designator Location")]
[Bindable(true)] /* Required for Enum Types */
[Browsable(true)]
public DesignatorAlignment DesignatorLocation
{
get { return designatorAlignment; }
set { designatorAlignment = value; }
}
#endregion
private bool IsLimitingChars(int textLength)
{
bool val = false;
if (charsLimited)
{
switch (inputType)
{
case TextBoxInputType.Default: val = Text.Length.Equals(maximumChars); break;
case TextBoxInputType.Numeric:
case TextBoxInputType.Currency:
if (useDecimals)
{
// Note: '+1' Refers the '.' that Separates the Decimals
val = Text.Length.Equals(maximumChars + decimalPlaces + 1);
}
else { val = Text.Length.Equals(maximumChars); }
break;
}
// case TextBoxInputType.IPV4: break;
}
return val;
}
private void SetDecimalValue()
{
Text_RemoveWhiteSpaces();
Text_SetDecimalValue();
Text_AddCurrencyDesignator();
}
#region <Overriden Events>
/// <summary> Occurs Before the Control Stops Being the Active Control. </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected override void OnValidating(CancelEventArgs e)
{
base.OnValidating(e);
switch (inputType)
{
// ...
case TextBoxInputType.IPV4:
// Validate the IPv4 Address
if (!HasValidIPAddress(Text)) { Text_SetDefaultValue(); }
break;
}
}
/// <summary> Occurs when a Keyboard Key is Pressed. </summary>
/// <param name="e"></param>
protected override void OnKeyPress(KeyPressEventArgs e)
{
base.OnKeyPress(e);
if (!e.KeyChar.Equals((char)Keys.Back))
{
// Limit Number of Characters
switch (inputType)
{
// case TextBoxInputType.Default: e.Handled = IsLimitingChars(Text.Length); break;
case TextBoxInputType.Numeric:
case TextBoxInputType.Currency:
e.Handled = !HasValidNumericChar(e.KeyChar) ^ IsLimitingChars(Text.Length);
if (e.KeyChar.Equals('.') & NrCharOccurrences('.') >= 1) { e.Handled = true; }
break;
// ...
}
}
}
/// <summary> Occurs when the Control Becomes the Active Control. </summary>
/// <param name="e"></param>
protected override void OnEnter(EventArgs e)
{
base.OnEnter(e);
switch (inputType)
{
// ...
case TextBoxInputType.Currency:
Text_RemoveWhiteSpaces();
Text_RemoveCurrencyDesignator();
break;
// ...
}
// Select the Text
SelectAll();
}
/// <summary> Occurs when the Control Stops Being the Active Control. </summary>
/// <param name="e"></param>
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
switch (inputType)
{
// ...
case TextBoxInputType.Currency:
SetDecimalValue();
break;
// ...
}
}
#endregion
#region <Methods> : (Validate Clipboard Data : On Paste)
protected override void WndProc(ref Message m)
{
/*
* Remarks: Handling Clipboard Data (Validate Data on Paste).
* Adapted Code from: 'Thorarin'.
* Source: https://stackoverflow.com/questions/15987712/handle-a-paste-event-in-c-sharp
*/
// 1. Handle All Other Messages Normally.
if (m.Msg != WM_PASTE) { base.WndProc(ref m); }
// 2. Handle Clipboard Data (On Paste).
else
{
if (Clipboard.ContainsText())
{
string val = Clipboard.GetText();
if (HasValidClipboardContent(val)) { Text = val; }
// Note(s):
// Text Validation for Each Input Type, Occurs under Control Leave Event.
// Clipboard.Clear(); --> You can use this if you Wish to Clear the Clipboard after Pasting the Value
}
}
}
#endregion
// 65666
#region <Methods>
/// <summary> Determines if the Clipboard Content Value is Valid. </summary>
/// <param name="val"></param>
/// <returns> True if Clipboard Content Matches the TextBox Input Requirements. </returns>
private bool HasValidClipboardContent(string val)
{
bool isValid = false;
switch (inputType)
{
case TextBoxInputType.Default: isValid = !IsLimitingChars(val.Length); break;
case TextBoxInputType.Numeric:
case TextBoxInputType.Currency:
isValid = !IsLimitingChars(val.Length) && IsNumericString(val);
break;
case TextBoxInputType.IPV4:
isValid = HasValidIPAddress(val);
break;
}
return isValid;
}
/// <summary> Determines if Specified Char Paramter is a Valid Numeric Character. </summary>
/// <param name="char"></param>
/// <returns> true if Received Char is a Number. </returns>
private bool HasValidNumericChar(char #char)
{
return allowedChars.Contains(#char) | #char.Equals((char)Keys.Back);
}
/// <summary> Determines if Received String Parameter is a Number. </summary>
/// <param name="value"></param>
/// <returns> True if Received String Parameter is a Number. </returns>
private bool IsNumericString(string value)
{
bool isNumeric = true;
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if (!HasValidNumericChar(c))
{
isNumeric = false;
break;
}
}
return isNumeric;
}
/// <summary> Determines if Specified Parameter String Contains a Valid IPv4 Address. </summary>
/// <returns> True if the IPv4 Address is Valid. </returns>
private bool HasValidIPAddress(string value)
{
// Remarks:
// Code based on Yiannis Leoussis Approach.
// Using a 'for' Loop instead of 'foreach'.
// Link: https://stackoverflow.com/questions/11412956/what-is-the-best-way-of-validating-an-ip-address
bool isValid = true;
if (string.IsNullOrWhiteSpace(Text)) { isValid = false; }
// Split string by ".", check that array length is 4
string[] arrOctets = Text.Split('.');
if (arrOctets.Length != 4) { isValid = false; }
// Check Each Sub-String (Ensure that it Parses to byte)
byte obyte = 0;
for (int i = 0; i < arrOctets.Length; i++)
{
string strOctet = arrOctets[i];
if (!byte.TryParse(strOctet, out obyte)) { isValid = false; }
}
// Set Default TextBox Text if IP is Invalid:
if (!isValid) { Text_SetDefaultValue(); }
return isValid;
}
/// <summary> Calculates the Nr. of Occurrences for the Specified Char Parameter. </summary>
/// <param name="char"></param>
/// <returns> The Number of the Received Char Parameter Occurrences Found in the TextBox Text. </returns>
private int NrCharOccurrences(char #char)
{
return Text.Split(#char).Length - 1;
}
/// <summary> Adds the Currency Symbol to the End of the TextBox Text. </summary>
private void Text_AddCurrencyDesignator()
{
// Add this to Control Event: Control_Leave
if (inputType.Equals(TextBoxInputType.Currency))
{
if (!string.IsNullOrEmpty(Text) & !string.IsNullOrWhiteSpace(Text))
{
TextAlign = HorizontalAlignment.Right;
switch (designatorAlignment)
{
case DesignatorAlignment.Left:
if (!Text.StartsWith(currencyDesignator))
{
Text = $"{currencyDesignator} {Text}";
}
break;
case DesignatorAlignment.Right:
if (!Text.EndsWith(currencyDesignator))
{
Text = $"{Text} {currencyDesignator}";
}
break;
}
}
}
Text_Align();
}
/// <summary> Remove the Currency Symbol to the End of the TextBox Text. </summary>
private void Text_RemoveCurrencyDesignator()
{
if (inputType.Equals(TextBoxInputType.Currency))
{
Text = Text.Replace(currencyDesignator, string.Empty);
}
}
/// <summary> Remove White Spaces from TextBox Text. </summary>
private void Text_RemoveWhiteSpaces()
{
if (inputType.Equals(TextBoxInputType.Currency) ^ inputType.Equals(TextBoxInputType.Numeric))
{
Text = Text.Replace(" ", string.Empty);
}
}
/// <summary> Align TextBox Text. </summary>
private void Text_Align()
{
switch (inputType)
{
case TextBoxInputType.Default: TextAlign = HorizontalAlignment.Left; break;
case TextBoxInputType.Numeric:
case TextBoxInputType.Currency: TextAlign = HorizontalAlignment.Right; break;
case TextBoxInputType.IPV4: TextAlign = HorizontalAlignment.Center; break;
}
}
/// <summary> Sets the Text Value as a Decimal Value by Inserting Missing Zeros. </summary>
private void Text_SetDecimalValue()
{
if (useDecimals)
{
decimal decVal = -1;
string val = string.Empty;
// Success:
// [Reference]: if (decimal.TryParse(Text, out decVal)) { val = decVal.ToString("0.00"); }
if (decimal.TryParse(Text, out decVal)) { val = decVal.ToString(decimalFormat); }
// else { /* FAIL */ }
// Set the Decimal Value as Text
Text = val;
}
}
/// <summary> Sets the Default Text Value to Each Input Type. </summary>
private void Text_SetDefaultValue()
{
switch (inputType)
{
case TextBoxInputType.Default: Text = string.Empty; break;
case TextBoxInputType.Numeric:
if (string.IsNullOrEmpty(Text)) { Text = "0"; }
else
{
if (IsNumericString(Text)) { Text = Text; }
}
break;
case TextBoxInputType.Currency:
if (string.IsNullOrEmpty(Text)) { Text = "0"; }
else
{
if (IsNumericString(Text))
{
Text_SetDecimalValue();
Text_AddCurrencyDesignator();
Text = Text;
}
}
break;
case TextBoxInputType.IPV4: Text = "0.0.0.0"; break;
}
}
#endregion
}
}
Further References:
Three dots in Textbox
What is the best way of validating an IP Address?
Handle a paste event in C#
I have a class that extends Combobox. In this class I can ONLY disable the togglebutton. I still want users to click into the combobox and type something in to filter its contents.
If you know any other way to destroy the togglebutton please let me know (as long as it doesn't involve recreating the template - I've tried and failed and the generated combobox template code is to large to dump in stackoverflow so I can't solve it that way. )
In the beginning I wanted to be able to toggle the togglebutton (if there less than 10 item left in the filter allow a user to click the togglebutton to show the list of items if there are more the togglebutton would disappear) . At this point I'll take any solution that shrinks, hides, moves, intentionally breaks, removes, buries alive, replaces either the togglebutton or the mouse on click event that tells the combobox that it should show its popup or anything in between that will stop this action.
This is my third question trying to find something that will work. Honestly any suggestions would be helpful (just no copy and edit the template please go to my other question where I ask help on how to implement that solution)
using Analytics_Module.Models;
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace Analytics_Module.UI_Components
{
class MultiselectFilteredComboBox : ComboBox
{
////
// Public Fields
////
/// <summary>
/// The search string treshold length.
/// </summary>
/// <remarks>
/// It's implemented as a Dependency Property, so you can set it in a XAML template
/// </remarks>
public static readonly DependencyProperty MinimumSearchLengthProperty =
DependencyProperty.Register(
"MinimumSearchLength",
typeof(int),
typeof(MultiselectFilteredComboBox),
new UIPropertyMetadata(3));
////
// Private Fields
////
/// <summary>
/// Caches the previous value of the filter.
/// </summary>
private string oldFilter = string.Empty;
/// <summary>
/// Holds the current value of the filter.
/// </summary>
private string currentFilter = string.Empty;
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
////
// Constructors
////
/// <summary>
/// Initializes a new instance of the FilteredComboBox class.
/// </summary>
/// <remarks>
/// You could set 'IsTextSearchEnabled' to 'false' here,
/// to avoid non-intuitive behavior of the control
/// </remarks>
public MultiselectFilteredComboBox()
{
}
////
// Properties
////
/// <summary>
/// Gets or sets the search string treshold length.
/// </summary>
/// <value>The minimum length of the search string that triggers filtering.</value>
[Description("Length of the search string that triggers filtering.")]
[Category("Filtered ComboBox")]
[DefaultValue(3)]
public int MinimumSearchLength
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (int)this.GetValue(MinimumSearchLengthProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(MinimumSearchLengthProperty, value);
}
}
/// <summary>
/// Gets a reference to the internal editable textbox.
/// </summary>
/// <value>A reference to the internal editable textbox.</value>
/// <remarks>
/// We need this to get access to the Selection.
/// </remarks>
protected TextBox EditableTextBox
{
get
{
return this.GetTemplateChild("PART_EditableTextBox") as TextBox;
}
}
////
// Event Raiser Overrides
////
/// <summary>
/// Keep the filter if the ItemsSource is explicitly changed.
/// </summary>
/// <param name="oldValue">The previous value of the filter.</param>
/// <param name="newValue">The current value of the filter.</param>
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += this.FilterPredicate;
}
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
/// <summary>
/// Confirm or cancel the selection when Tab, Enter, or Escape are hit.
/// Open the DropDown when the Down Arrow is hit.
/// </summary>
/// <param name="e">Key Event Args.</param>
/// <remarks>
/// The 'KeyDown' event is not raised for Arrows, Tab and Enter keys.
/// It is swallowed by the DropDown if it's open.
/// So use the Preview instead.
/// </remarks>
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Tab || e.Key == Key.Enter)
{
// Explicit Selection -> Close ItemsPanel
this.IsDropDownOpen = false;
}
else if (e.Key == Key.Escape)
{
// Escape -> Close DropDown and redisplay Filter
this.IsDropDownOpen = false;
this.SelectedIndex = -1;
this.Text = this.currentFilter;
}
else
{
if (e.Key == Key.Down)
{
// Arrow Down -> Open DropDown
this.IsDropDownOpen = true;
}
base.OnPreviewKeyDown(e);
}
// Cache text
this.oldFilter = this.Text;
}
/// <summary>
/// Modify and apply the filter.
/// </summary>
/// <param name="e">Key Event Args.</param>
/// <remarks>
/// Alternatively, you could react on 'OnTextChanged', but navigating through
/// the DropDown will also change the text.
/// </remarks>
protected override void OnKeyUp(KeyEventArgs e)
{
if (e.Key == Key.Up || e.Key == Key.Down)
{
// Navigation keys are ignored
}
else if (e.Key == Key.Tab || e.Key == Key.Enter)
{
// Explicit Select -> Clear Filter
this.ClearFilter();
}
else
{
// The text was changed
if (this.Text != this.oldFilter)
{
// Clear the filter if the text is empty,
// apply the filter if the text is long enough
if (this.Text.Length == 0 || this.Text.Length >= this.MinimumSearchLength)
{
this.RefreshFilter();
this.IsDropDownOpen = true;
// Unselect
this.EditableTextBox.SelectionStart = int.MaxValue;
}
}
base.OnKeyUp(e);
// Update Filter Value
this.currentFilter = this.Text;
}
}
/// <summary>
/// Make sure the text corresponds to the selection when leaving the control.
/// </summary>
/// <param name="e">A KeyBoardFocusChangedEventArgs.</param>
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
//this.ClearFilter();
//int temp = this.SelectedIndex;
//this.SelectedIndex = -1;
//this.Text = string.Empty;
//this.SelectedIndex = temp;
//base.OnPreviewLostKeyboardFocus(e);
}
////
// Helpers
////
/// <summary>
/// Re-apply the Filter.
/// </summary>
private void RefreshFilter()
{
if (this.ItemsSource != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(this.ItemsSource);
view.Refresh();
}
}
/// <summary>
/// Clear the Filter.
/// </summary>
private void ClearFilter()
{
this.currentFilter = string.Empty;
this.RefreshFilter();
}
/// <summary>
/// The Filter predicate that will be applied to each row in the ItemsSource.
/// </summary>
/// <param name="value">A row in the ItemsSource.</param>
/// <returns>Whether or not the item will appear in the DropDown.</returns>
private bool FilterPredicate(object value)
{
MultiSelectDropDownListEntry tmp = (MultiSelectDropDownListEntry)value;
// No filter, no text
if (value == null)
{
return false;
}
// No text, no filter
if (this.Text.Length == 0)
{
return true;
}
// Case insensitive search
return tmp.Name.ToString().ToLower().Contains(this.Text.ToLower());
}
}
}
Found a way.
I added this to the class
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
(((this.GetVisualChild(0) as Grid).Children)[1] as System.Windows.Controls.Primitives.ToggleButton).IsEnabled = false;
}
My combobox returns a set of values from s stored procedure as this
private void BindCombo()
{
DataCombo.FillCombo(ComboDS(2313001), cmbClass, 0);
DataCombo.FillCombo(DDCombo(5007), cmbGroup, 0);
}
I managed to give a rudimentary auto complete suggestion as IsTextSearchenabled but cannot get a auto suggestion box that i would like.
I have seen loads of examples of autocomplete/suggestive textboxes but none of them seem to suit me.
this code apparently suits me.
but how would i use the auto suggest here
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace DotNetZen.AutoFilteredComboBox
{
public class AutoFilteredComboBox : ComboBox
{
private int silenceEvents = 0;
/// <summary>
/// Creates a new instance of <see cref="AutoFilteredComboBox" />.
/// </summary>
public AutoFilteredComboBox()
{
DependencyPropertyDescriptor textProperty = DependencyPropertyDescriptor.FromProperty(
ComboBox.TextProperty, typeof(AutoFilteredComboBox));
textProperty.AddValueChanged(this, this.OnTextChanged);
this.RegisterIsCaseSensitiveChangeNotification();
}
#region IsCaseSensitive Dependency Property
/// <summary>
/// The <see cref="DependencyProperty"/> object of the <see cref="IsCaseSensitive" /> dependency property.
/// </summary>
public static readonly DependencyProperty IsCaseSensitiveProperty =
DependencyProperty.Register("IsCaseSensitive", typeof(bool), typeof(AutoFilteredComboBox), new UIPropertyMetadata(false));
/// <summary>
/// Gets or sets the way the combo box treats the case sensitivity of typed text.
/// </summary>
/// <value>The way the combo box treats the case sensitivity of typed text.</value>
[System.ComponentModel.Description("The way the combo box treats the case sensitivity of typed text.")]
[System.ComponentModel.Category("AutoFiltered ComboBox")]
[System.ComponentModel.DefaultValue(true)]
public bool IsCaseSensitive
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (bool)this.GetValue(IsCaseSensitiveProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(IsCaseSensitiveProperty, value);
}
}
protected virtual void OnIsCaseSensitiveChanged(object sender, EventArgs e)
{
if (this.IsCaseSensitive)
this.IsTextSearchEnabled = false;
this.RefreshFilter();
}
private void RegisterIsCaseSensitiveChangeNotification()
{
System.ComponentModel.DependencyPropertyDescriptor.FromProperty(IsCaseSensitiveProperty, typeof(AutoFilteredComboBox)).AddValueChanged(
this, this.OnIsCaseSensitiveChanged);
}
#endregion
#region DropDownOnFocus Dependency Property
/// <summary>
/// The <see cref="DependencyProperty"/> object of the <see cref="DropDownOnFocus" /> dependency property.
/// </summary>
public static readonly DependencyProperty DropDownOnFocusProperty =
DependencyProperty.Register("DropDownOnFocus", typeof(bool), typeof(AutoFilteredComboBox), new UIPropertyMetadata(true));
/// <summary>
/// Gets or sets the way the combo box behaves when it receives focus.
/// </summary>
/// <value>The way the combo box behaves when it receives focus.</value>
[System.ComponentModel.Description("The way the combo box behaves when it receives focus.")]
[System.ComponentModel.Category("AutoFiltered ComboBox")]
[System.ComponentModel.DefaultValue(true)]
public bool DropDownOnFocus
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (bool)this.GetValue(DropDownOnFocusProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(DropDownOnFocusProperty, value);
}
}
#endregion
#region | Handle selection |
/// <summary>
/// Called when <see cref="ComboBox.ApplyTemplate()"/> is called.
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.EditableTextBox.SelectionChanged += this.EditableTextBox_SelectionChanged;
}
/// <summary>
/// Gets the text box in charge of the editable portion of the combo box.
/// </summary>
protected TextBox EditableTextBox
{
get
{
return ((TextBox)base.GetTemplateChild("PART_EditableTextBox"));
}
}
private int start = 0, length = 0;
private void EditableTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
if (this.silenceEvents == 0)
{
this.start = ((TextBox)(e.OriginalSource)).SelectionStart;
this.length = ((TextBox)(e.OriginalSource)).SelectionLength;
this.RefreshFilter();
}
}
#endregion
#region | Handle focus |
/// <summary>
/// Invoked whenever an unhandled <see cref="UIElement.GotFocus" /> event
/// reaches this element in its route.
/// </summary>
/// <param name="e">The <see cref="RoutedEventArgs" /> that contains the event data.</param>
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (this.ItemsSource != null && this.DropDownOnFocus)
{
this.IsDropDownOpen = true;
}
}
#endregion
#region | Handle filtering |
private void RefreshFilter()
{
if (this.ItemsSource != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(this.ItemsSource);
view.Refresh();
this.IsDropDownOpen = true;
}
}
private bool FilterPredicate(object value)
{
// We don't like nulls.
if (value == null)
return false;
// If there is no text, there's no reason to filter.
if (this.Text.Length == 0)
return true;
string prefix = this.Text;
// If the end of the text is selected, do not mind it.
if (this.length > 0 && this.start + this.length == this.Text.Length)
{
prefix = prefix.Substring(0, this.start);
}
return value.ToString()
.StartsWith(prefix, !this.IsCaseSensitive, CultureInfo.CurrentCulture);
}
#endregion
/// <summary>
/// Called when the source of an item in a selector changes.
/// </summary>
/// <param name="oldValue">Old value of the source.</param>
/// <param name="newValue">New value of the source.</param>
protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
{
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += this.FilterPredicate;
}
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
private void OnTextChanged(object sender, EventArgs e)
{
if (!this.IsTextSearchEnabled && this.silenceEvents == 0)
{
this.RefreshFilter();
// Manually simulate the automatic selection that would have been
// available if the IsTextSearchEnabled dependency property was set.
if (this.Text.Length > 0)
{
foreach (object item in CollectionViewSource.GetDefaultView(this.ItemsSource))
{
int text = item.ToString().Length, prefix = this.Text.Length;
this.SelectedItem = item;
this.silenceEvents++;
this.EditableTextBox.Text = item.ToString();
this.EditableTextBox.Select(prefix, text - prefix);
this.silenceEvents--;
break;
}
}
}
}
}
}
Also found the AutoFilteredComboBox to be very simple to work with. Though I have made some changes:
Removed use of DependencyPropertyDescriptor to avoid memory leak of combobox-objects
Introduced FilterItem-event and FilterList-event to allow one to customize the filtering
Changed default filtering from starts-with-string to contains-string
Removed support of having IsTextSearchEnabled enabled
Shows dropdown as soon one changes the search string, so search result is displayed
Example of how it is used:
<Controls:AutoFilteredComboBox ItemsSource="{Binding ViewModel.AvailableItems}"
SelectedValue="{Binding ViewModel.SelectedItem, Mode=TwoWay}"
IsEditable="True" IsTextSearchEnabled="False"/>
Improved version of AutoFilteredComboBox:
public class AutoFilteredComboBox : ComboBox
{
bool _ignoreTextChanged;
string _currentText;
/// <summary>
/// Creates a new instance of <see cref="AutoFilteredComboBox" />.
/// </summary>
public AutoFilteredComboBox()
{
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) return;
}
public event Func<object, string, bool> FilterItem;
public event Action<string> FilterList;
#region IsCaseSensitive Dependency Property
/// <summary>
/// The <see cref="DependencyProperty"/> object of the <see cref="IsCaseSensitive" /> dependency property.
/// </summary>
public static readonly DependencyProperty IsCaseSensitiveProperty =
DependencyProperty.Register("IsCaseSensitive", typeof(bool), typeof(AutoFilteredComboBox), new UIPropertyMetadata(false));
/// <summary>
/// Gets or sets the way the combo box treats the case sensitivity of typed text.
/// </summary>
/// <value>The way the combo box treats the case sensitivity of typed text.</value>
[Description("The way the combo box treats the case sensitivity of typed text.")]
[Category("AutoFiltered ComboBox")]
[DefaultValue(true)]
public bool IsCaseSensitive
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (bool)this.GetValue(IsCaseSensitiveProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(IsCaseSensitiveProperty, value);
}
}
#endregion
#region DropDownOnFocus Dependency Property
/// <summary>
/// The <see cref="DependencyProperty"/> object of the <see cref="DropDownOnFocus" /> dependency property.
/// </summary>
public static readonly DependencyProperty DropDownOnFocusProperty =
DependencyProperty.Register("DropDownOnFocus", typeof(bool), typeof(AutoFilteredComboBox), new UIPropertyMetadata(false));
/// <summary>
/// Gets or sets the way the combo box behaves when it receives focus.
/// </summary>
/// <value>The way the combo box behaves when it receives focus.</value>
[Description("The way the combo box behaves when it receives focus.")]
[Category("AutoFiltered ComboBox")]
[DefaultValue(false)]
public bool DropDownOnFocus
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (bool)this.GetValue(DropDownOnFocusProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(DropDownOnFocusProperty, value);
}
}
#endregion
#region | Handle focus |
/// <summary>
/// Invoked whenever an unhandled <see cref="UIElement.GotFocus" /> event
/// reaches this element in its route.
/// </summary>
/// <param name="e">The <see cref="RoutedEventArgs" /> that contains the event data.</param>
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (this.ItemsSource != null && this.DropDownOnFocus)
{
this.IsDropDownOpen = true;
}
}
#endregion
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
AddHandler(TextBox.TextChangedEvent, new TextChangedEventHandler(OnTextChanged));
KeyUp += AutoFilteredComboBox_KeyUp;
this.IsTextSearchEnabled = false;
}
void AutoFilteredComboBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down)
{
if (this.IsDropDownOpen == true)
{
// Ensure that focus is given to the dropdown list
if (Keyboard.FocusedElement is TextBox)
{
Keyboard.Focus(this);
if (this.Items.Count > 0)
{
if (this.SelectedIndex == -1 || this.SelectedIndex==0)
this.SelectedIndex = 0;
}
}
}
}
if (Keyboard.FocusedElement is TextBox)
{
if (e.OriginalSource is TextBox)
{
// Avoid the automatic selection of the first letter (As next letter will cause overwrite)
TextBox textBox = e.OriginalSource as TextBox;
if (textBox.Text.Length == 1 && textBox.SelectionLength == 1)
{
textBox.SelectionLength = 0;
textBox.SelectionStart = 1;
}
}
}
}
#region | Handle filtering |
private void RefreshFilter()
{
if (this.ItemsSource != null)
{
Action<string> filterList = FilterList;
if (filterList != null)
{
filterList(_currentText);
}
else
{
ICollectionView view = CollectionViewSource.GetDefaultView(this.ItemsSource);
view.Refresh();
}
this.SelectedIndex = -1; // Prepare so arrow down selects first
this.IsDropDownOpen = true;
}
}
private bool FilterPredicate(object value)
{
// We don't like nulls.
if (value == null)
return false;
// If there is no text, there's no reason to filter.
if (string.IsNullOrEmpty(_currentText))
return true;
Func<object, string, bool> filterItem = FilterItem;
if (filterItem != null)
return filterItem(value, _currentText);
if (IsCaseSensitive)
return value.ToString().Contains(_currentText);
else
return value.ToString().ToUpper().Contains(_currentText.ToUpper());
}
#endregion
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
try
{
_ignoreTextChanged = true; // Ignore the following TextChanged
base.OnSelectionChanged(e);
}
finally
{
_ignoreTextChanged = false;
}
}
/// <summary>
/// Called when the source of an item in a selector changes.
/// </summary>
/// <param name="oldValue">Old value of the source.</param>
/// <param name="newValue">New value of the source.</param>
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
if (FilterList == null)
view.Filter += this.FilterPredicate;
}
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
if (_ignoreTextChanged)
return;
_currentText = Text;
if (!this.IsTextSearchEnabled)
{
this.RefreshFilter();
}
}
I have found a super simple workaround to my problem.
i created a preview text input event of the combobox.
then i just wrote
Combobox.IsDropDownOpen = true
may not the most elegant but works in my case
I was wondering if anyone could show me a simple way of creating buttons in XNA.
Preferably a way to add a function when its clicked, and a way to add and remove them easily.
I recommend using the NeoForce Controls Library for GUI related problems - it has buttons, among the other useful GUI controls including popup windows, list views, combo boxes, and so on.
If you are writing a button class for the learning experience... well, try learning more about it yourself via Google before asking for help.
ADDENDUM
This is some code that I've written for buttons. Maybe it can serve as a starting point. I use it in my 2D game engine, so it has been debugged and tested.
/// <summary>
/// A control that changes appearance when it is hovered over and triggers some effect when it is clicked
/// </summary>
public class EnigmaButton
{
/// <summary>
/// The method signature for notification when a button is clicked
/// </summary>
/// <param name="sender">EnigmaButton that was clicked</param>
public delegate void OnClickEvent(EnigmaButton sender);
/// <summary>
/// Types of textures used for Enigma Buttons
/// </summary>
public enum TextureType { Normal, Over, Press }
#region Variables
protected IVisualExposer m_ui;
protected Rectangle m_bounds;
IInputExposer m_input;
bool m_over = false, m_press = false, m_wasPressed = false;
Dictionary<TextureType, EnigmaResource<Texture2D>> m_textures;
string m_text, m_name;
EnigmaResource<SpriteFont> m_font;
int m_minTextShadow, m_maxTextShadow;
Color m_textTint;
public event OnClickEvent OnClick;
#endregion
/// <summary>
/// A control that changes appearance when it is hovered over and triggers some effect when it is clicked
/// </summary>
/// <param name="ui">Graphical assets</param>
/// <param name="input">Input exposer for mouse input and XBox controller input</param>
/// <param name="reader">XMLReader for the definition of the controller</param>
/// <param name="pos">Bounds of the controller</param>
public EnigmaButton(IVisualExposer ui, IInputExposer input, XmlReader reader, Rectangle pos)
{
m_ui = ui;
m_bounds = pos;
m_textures = new Dictionary<TextureType, EnigmaResource<Texture2D>>();
m_input = input;
Enabled = true;
#region Reading
string name;
bool started = false, insideText = false;
while (reader.Read())
{
if (reader.MoveToContent() == XmlNodeType.Element)
{
name = reader.Name.ToLower();
if (name == "button")
{
if (started)
throw new Exception("Already started.");
started = true;
m_name = reader.GetAttribute("name") ?? string.Empty;
}
else if (!started)
throw new Exception("Not started");
else if (name == "text")
{
m_font = new EnigmaResource<SpriteFont>();
m_font.Filepath = reader.GetAttribute("font");
string minShadow = reader.GetAttribute("minShadow"), maxShadow = reader.GetAttribute("maxShadow");
m_minTextShadow = minShadow != null ? int.Parse(minShadow) : 0;
m_maxTextShadow = maxShadow != null ? int.Parse(maxShadow) : 2;
m_text = reader.GetAttribute("text") ?? string.Empty;
insideText = true;
m_textTint = Color.White;
}
else if (name == "bounds")
{
insideText = false;
m_bounds = new Rectangle(int.Parse(reader.GetAttribute("x")), int.Parse(reader.GetAttribute("y")),
int.Parse(reader.GetAttribute("width")), int.Parse(reader.GetAttribute("height")));
}
else if (name == "texture")
{
insideText = false;
TextureType texType = (TextureType)Enum.Parse(typeof(TextureType), reader.GetAttribute("type"));
if (m_textures.ContainsKey(texType))
throw new Exception("A texture of type '" + texType.ToString() + "' cannot be registered twice");
EnigmaResource<Texture2D> res = new EnigmaResource<Texture2D>();
res.Filepath = reader.ReadString();
m_textures.Add(texType, res);
}
else if (name == "tint")
{
if (!insideText)
throw new Exception("Tints can only be for text");
float a, r, g, b;
string[] split = reader.ReadString().Split(',');
if (split.Length != 4)
throw new Exception("Colors must be RGBA");
r = float.Parse(split[0].Trim());
g = float.Parse(split[1].Trim());
b = float.Parse(split[2].Trim());
a = float.Parse(split[3].Trim());
m_textTint = new Color(r, g, b, a);
}
}
}
#endregion
if (!m_textures.ContainsKey(TextureType.Normal))
throw new Exception("A button must have at least a '" + TextureType.Normal.ToString() + "' texture");
}
#region Methods
public void Initialize()
{
}
public void LoadContent()
{
EnigmaResource<Texture2D> res;
for (int i = 0; i < m_textures.Count; i++)
{
res = m_textures[m_textures.ElementAt(i).Key];
res.Resource = m_ui.Content.Load<Texture2D>(res.Filepath);
m_textures[m_textures.ElementAt(i).Key] = res;
}
if (m_font.Filepath != null)
m_font.Resource = m_ui.Content.Load<SpriteFont>(m_font.Filepath);
}
public void Update(GameTime gameTime)
{
m_wasPressed = m_press;
m_over = m_bounds.Contains(m_input.MouseX, m_input.MouseY);
m_press = m_over ? m_wasPressed ? m_input.IsMouseLeftPressed || m_input.IsButtonPressed(Buttons.A) : m_input.IsMouseLeftTriggered || m_input.IsButtonTriggered(Buttons.A) : false;
if (!m_wasPressed && m_press && OnClick != null)
OnClick(this);
}
public void Draw(GameTime gameTime)
{
Texture2D toDraw = m_textures[TextureType.Normal].Resource;
if (Enabled)
{
if (m_press && m_textures.ContainsKey(TextureType.Press))
toDraw = m_textures[TextureType.Press].Resource;
else if (m_over && m_textures.ContainsKey(TextureType.Over))
toDraw = m_textures[TextureType.Over].Resource;
}
m_ui.SpriteBatch.Draw(toDraw, m_bounds, Enabled ? Color.White : Color.Gray);
if (m_font.Resource != null)
{
Vector2 pos = new Vector2(m_bounds.X, m_bounds.Y);
Vector2 size = m_font.Resource.MeasureString(m_text);
pos.X += (m_bounds.Width - size.X) / 2;
pos.Y += (m_bounds.Height - size.Y) / 2;
UIHelper.DrawShadowedString(m_ui, m_font.Resource, m_text, pos, m_textTint, m_minTextShadow, m_maxTextShadow);
}
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the name of the button
/// </summary>
public string Name
{
get { return m_name; }
set { m_name = value ?? m_name; }
}
/// <summary>
/// Gets or sets the text drawn in the button.
/// WARNING: Will overflow if the text does not normally fit.
/// </summary>
public string Text
{
get { return m_text; }
set { m_text = value ?? string.Empty; }
}
/// <summary>
/// Gets or sets the bounds of the button
/// </summary>
public Rectangle Bounds
{
get { return m_bounds; }
set { m_bounds = value; }
}
/// <summary>
/// Whether or not the control is enabled
/// </summary>
public bool Enabled { get; set; }
#endregion
}
I've looked all over the place, but it seems that examples I have seen allow only numbers 0-9
I'm writing a Pythagorean Theorem program. I wish to have the phone (Windows Phone 7) check if there are ANY alpha (A-Z, a-z), symbols (#,%), or anything other than a number in the textbox. If not, then it will continue computing. I want to check so there will be no future errors.
This is basically a bad pseudocode of what I want it to do
txtOne-->any alpha?--No-->any symbols--No-->continue...
I would actually prefer a command to check if the string is completely a number.
Thanks in advance!
An even better way to ensure that your textbox is a number is to handle the KeyPress event. You can then choose what characters you want to allow. In the following example we disallow all characters that are not digits:
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
// If the character is not a digit, don't let it show up in the textbox.
if (!char.IsDigit(e.KeyChar))
e.Handled = true;
}
This ensures that your textbox text is a number because it only allows digits to be entered.
This is something I just came up with to allow decimal values (and apparently the backspace key):
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
if (char.IsDigit(e.KeyChar))
{
return;
}
if (e.KeyChar == (char)Keys.Back)
{
return;
}
if (e.KeyChar == '.' && !textBox1.Text.Contains('.'))
{
return;
}
e.Handled = true;
}
There are several ways to do this:
You can use TryParse() and check if the return value is not false.
You can use Regex to validate:
Match match = Regex.Match(textBox.Text, #"^\d+$");
if (match.Success) { ... }
// or
if (Regex.IsMatch(textBox.Text, #"^\d+$")) { ... }
Or you can simply only give them the Numeric keyboard. There are several different keyboard layouts you can use. Link
If you want to do more indepth, I have used the KeyDown and KeyUp events to check what was entered and handle the keypress. Link
You can define the textbox's input scope.
Example:
<TextBox InputScope="Digits"></TextBox>
You can use TryParse and see if there is a result.
See http://msdn.microsoft.com/en-us/library/system.int64.tryparse.aspx
Int64 output;
if (!Int64.TryParse(input, out output)
{
ShowErrorMessage();
return
}
Continue..
/// <summary>
/// A numeric-only textbox.
/// </summary>
public class NumericOnlyTextBox : TextBox
{
#region Properties
#region AllowDecimals
/// <summary>
/// Gets or sets a value indicating whether [allow decimals].
/// </summary>
/// <value>
/// <c>true</c> if [allow decimals]; otherwise, <c>false</c>.
/// </value>
public bool AllowDecimals
{
get { return (bool)GetValue(AllowDecimalsProperty); }
set { SetValue(AllowDecimalsProperty, value); }
}
/// <summary>
/// The allow decimals property
/// </summary>
public static readonly DependencyProperty AllowDecimalsProperty =
DependencyProperty.Register("AllowDecimals", typeof(bool),
typeof(NumericOnlyTextBox), new UIPropertyMetadata(false));
#endregion
#region MaxValue
/// <summary>
/// Gets or sets the max value.
/// </summary>
/// <value>
/// The max value.
/// </value>
public double? MaxValue
{
get { return (double?)GetValue(MaxValueProperty); }
set { SetValue(MaxValueProperty, value); }
}
/// <summary>
/// The max value property
/// </summary>
public static readonly DependencyProperty MaxValueProperty =
DependencyProperty.Register("MaxValue", typeof(double?),
typeof(NumericOnlyTextBox), new UIPropertyMetadata(null));
#endregion
#region MinValue
/// <summary>
/// Gets or sets the min value.
/// </summary>
/// <value>
/// The min value.
/// </value>
public double? MinValue
{
get { return (double?)GetValue(MinValueProperty); }
set { SetValue(MinValueProperty, value); }
}
/// <summary>
/// The min value property
/// </summary>
public static readonly DependencyProperty MinValueProperty =
DependencyProperty.Register("MinValue", typeof(double?),
typeof(NumericOnlyTextBox), new UIPropertyMetadata(null));
#endregion
#endregion
#region Contructors
/// <summary>
/// Initializes a new instance of the <see cref="NumericOnlyTextBox" /> class.
/// </summary>
public NumericOnlyTextBox()
{
this.PreviewTextInput += OnPreviewTextInput;
}
#endregion
#region Methods
/// <summary>
/// Numeric-Only text field.
/// </summary>
/// <param name="text">The text.</param>
/// <returns></returns>
public bool NumericOnlyCheck(string text)
{
// regex that matches disallowed text
var regex = (AllowDecimals) ? new Regex("[^0-9.]+") : new Regex("[^0-9]+");
return !regex.IsMatch(text);
}
#endregion
#region Events
/// <summary>
/// Called when [preview text input].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="TextCompositionEventArgs" /> instance
/// containing the event data.</param>
/// <exception cref="System.NotImplementedException"></exception>
private void OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
// Check number
if (this.NumericOnlyCheck(e.Text))
{
// Evaluate min value
if (MinValue != null && Convert.ToDouble(this.Text + e.Text) < MinValue)
{
this.Text = MinValue.ToString();
this.SelectionStart = this.Text.Length;
e.Handled = true;
}
// Evaluate max value
if (MaxValue != null && Convert.ToDouble(this.Text + e.Text) > MaxValue)
{
this.Text = MaxValue.ToString();
this.SelectionStart = this.Text.Length;
e.Handled = true;
}
}
else
{
e.Handled = true;
}
}
#endregion
}