WPF Text Box allowing decimal entries - wpf

I am new to WPF (VS2013, .NET Framework 4.5) and what was a big suprise to me is that I can not enter a float value to my text box.
My text box is bound to a float variable. If that variable defaults to say 5, then text box will show 5. But I have an issue:
I can not enter . if I want to have say 5.5. To solve this, I found someone's suggestion to add to xaml for text box binding "StringFormat=N2".
That created even more issues:
1. Now even if I have even number like 5, it shows as 5.00
2. If I put cursor btw 2 zeros or anywhere else, neither backspace nor delete key will delete the entrie
3. If I put cursor btw 5 and . (in 5.00) and type ".10", I end up with 5.10.00.
Is it possible that number entry in WPF is so complicated??? All I want is to enter a number that will be stored in a float.
If I enter 5, it should show as 5 in TextBox.
If I enter 5.05, than that is how it should show in TextBox.
The underlying float variable can hold both 5 and 5.05, both as a float.
UPDATE:
Here is my code
private float age;
public float Age {
get { return age; }
set
{
if (value <= 0 || value > 120)
{
throw new ArgumentException("The age must be between 0 and 120 years");
}
age= value;
}
}
and in XAML:
<TextBox Name="txtAge" Text="{Binding Age, UpdateSourceTrigger=PropertyChanged, StringFormat=N1, ValidatesOnExceptions=True}" />

From Standard Numeric Format Strings
The number is converted to a string of the form "-d,ddd,ddd.ddd…",
where '-' indicates a negative number symbol if required, 'd'
indicates a digit (0-9), ',' indicates a thousand separator between
number groups, and '.' indicates a decimal point symbol.
It would seem that N will include thousands separators.
See also Custom Numeric Format Strings

There is no binding in your code. Note the Path=Age :
<TextBox Grid.Row="0" Text="{Binding Path=Age, UpdateSourceTrigger=PropertyChanged, StringFormat=N1, ValidatesOnExceptions=True}"/>
The binding between the model view with the view model:
var myModel = new ViewModel();
control.DataContext = myModel;
Then the ViewModel (implement the INotifyPropertyChanged interface)
public class ViewModel : INotifyPropertyChanged
{
private float _age;
public float Age
{
get { return _age; }
set
{
if (value.Equals(_age)) return;
_age = value;
OnPropertyChanged("Age");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
You should be interested in MVVM literature:
MVVM overview
MVVM by Microsoft Patterns & Practices
As the other answer suggests, you may have a mismatch between what you expect to be the decimal separator (.) and what your system consider as the decimal separator (maybe something else like ,).

I found the solution on this site, the trick is to force the behavior of framework 4
If you still want to use ‘UpdateSourceTrigger = PropertyChanged’, you can force the .NET 4 behavior in your .NET 4.5 application by adding the following line of code to the constructor of your App.xaml.cs:
public App()
{
System.Windows.FrameworkCompatibilityPreferences
.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
}
Source:
http://www.sebastianlux.net/2014/06/21/binding-float-values-to-a-net-4-5-wpf-textbox-using-updatesourcetriggerpropertychanged/

Related

TextBox not updating properly when decimal point is input

I'm having some strange behavior that I can't quite figure out. I have a text box bound to a string property (that represents a numeric value). The getter should be formatting my string to include 4 decimal digits. This much works but gives the user non-intuitive surprise when entering a number with decimal values.
Example: If the user wanted to enter 5.1, they type 5 -> . -> 1 resulting in the TextBox formatting to
"5.0000"->"5..0000"->"5.1.0000"
So I added additional logic to replace any sequential decimal points with a single decimal point. This is where my strange issue have started appearing. A user can enter "5.....1" and the UI does not seem to update with the removal of the sequential decimal points. However typing any character between those decimal points triggers it to correctly remove sequential decimal points:
Example: Typing a 2 before the last decimal point ("5....2.1") results
in "5.2.1", which is what I expect.
If I step trough the code, the returned value always seems to be correctly formatted with no sequential decimal points. However the UI still displays "5.....1". The UI doesn't seem to update if I edit the decimal points directly (either typing more or deleting existing ones), however typing or deleting a character in between them triggers the expected update.
This is the property in the ViewModel:
Private _valueString As String
Public Property ValueString As String
Get
Dim formattedString As String = Double.NaN.ToString
Dim tempDouble As Double
Dim rgx As New Regex("\.+")
If Double.TryParse(_ValueString, tempDouble) Then
formattedString = tempDouble.ToString("F4")
formattedString = rgx.Replace(formattedString, ".")
Else
formattedString = rgx.Replace(_valueString, ".")
End If
Return formattedString
End Get
Set(value As String)
Dim rgx As New Regex("\.+")
_ValueString = rgx.Replace(value, ".")
NotifyPropertyChanged("ValueString")
End Set
End Property
XAML:
<TextBox Text="{Binding ValueString, UpdateSourceTrigger=PropertyChanged}" />
Any ideas on what is causing it to not update? Why are users able to enter multiple decimal points in a row?
Try using Text="{Binding ValueString, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Apart from that I think you should just bind the Textbox to a double Property and use a validator so that input thats unable to parse as double is not set at all.
I would set your updatesourcetrigger to LostFocus (default) and set up the following in the textbox's textchanged event. This will allow you to continue entering numbers after the decimal place. If there is no decimal place on the end it will update just like if you had set the trigger to PropertyChanged.
The update source function is manually doing what the trigger would fire.
For the sake of this example my textbox is called txbAmount.
VB
Private Sub txbAmount_TextChanged(sender As Object, e As TextChangedEventArgs)
If Me.txbAmount.Text IsNot Nothing Then
If Me.txbAmount.Text.Count() > 0 Then
If Me.txbAmount.Text.Last() <> "."C Then
Me.txbAmount.GetBindingExpression(TextBox.TextProperty).UpdateSource()
End If
End If
End If
End Sub
C#
private void txbAmount_TextChanged(object sender, TextChangedEventArgs e)
{
if (this.txbAmount.Text != null)
{
if (this.txbAmount.Text.Count() > 0)
{
if (this.txbAmount.Text.Last() != '.')
{
this.txbAmount.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
}
}

Binding from integer number of days to the WPF UserControl showing both value and unit (Day, Week, Month)

In our WPF application which follows MVVM pattern, there is one property in the ViewModel, which is an integer value representing the number of days. And we would like to present this value in such pattern in WPF: it should contain one TextBox and one ComboBox, where the text in TextBox represents the values of the time and the value in ComboBox represents the unit. For example, property value 5 will show as [5][Day], while value 14 shows as [2][Week]. This should be a two-way binding since we want the user to modify it too. Of course such mapping is not one-to-one, 14 can also be represented to [14][Day]. But the principle is the bigger unit has higher priority.
Now weirdly I don't even know what to start with, especially when I want to follow MVVM. Because I only have one value which binds to to destination. And since it is such a simple value, there is no point to create a class as the DataContext of such UserControl
Anyone can give me some hint? Thanks!
All this work should be done in ViewModel, make the initial integer property private, add two new properties UnitsCount and UnitsType.
private UnitsTypeEnum unitsType = UnitsTypeEnum.Days;
private int unitsCount = 0;
public int UnitsCount
{
get { return unitsCount; }
set
{
/* set the number of days based on this new value and UnitsType */
unitsCount = value;
NotifyPropertyChanged("UnitsCount");
}
}
public UnitsTypeEnum UnitsType
{
get { return unitsType; }
set
{
/* set the number of days based on this new value and UnitsCount */
unitsType = value;
NotifyPropertyChanged("UnitsType");
}
}
Now make sure in constructor you initialize correctly the UnitsCount and UnitsType properties for the first time.

Setting the Text property of a DropDownList ComboBox to an invalid value doesn't raise exception?

I'm looking at ways to resolve an issue with a Winforms application, which uses a ComboBox control. Specifically, the ComboBox (Style=DropDownList) is bound to a datasource and, as the user navigates through some other data, the "Text" property of the ComboBox property is set - and the user can select some other value.
The trouble starts when the value I set the "Text" property to is not in the list of available items. It seems that nothing happens. Take the following simple example:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
myComboBox1.DropDownStyle = ComboBoxStyle.DropDownList;
//myComboBox1.Items.AddRange(new[] { "One", "Two", "Three" });
List<KeyValuePair<Int32, String>> values = new List<KeyValuePair<Int32, String>>();
values.Add(new KeyValuePair<Int32, String>(1, "One"));
values.Add(new KeyValuePair<Int32, String>(2, "Two"));
values.Add(new KeyValuePair<Int32, String>(3, "Three"));
myComboBox1.DataSource = values;
myComboBox1.ValueMember = "Key";
myComboBox1.DisplayMember = "Value";
button1.Click += (s, e) => { myComboBox1.Text = "Four"; };
button2.Click += (s, e) => { myComboBox1.SelectedIndex -= 1; };
}
}
public class MyComboBox : System.Windows.Forms.ComboBox
{
public override string Text
{
get { return base.Text; }
set { MessageBox.Show(value); base.Text = value; }
}
}
This technique is used throughout a large application, so when it was noticed that (in the example above) setting the "Text" to "Four" does nothing, I thought that maybe I could trap this and throw an exception. In reality, the application is peppered with code like this:
if (myDataRow.IsBlahNull())
myComboBox1.Text = "";
else
myComboBox1.Text = myDataRow.Blah;
Now, while I appreciate that setting "SelectedIndex = -1" would be better for the "IsNull" case, the fact remains that myDataRow.Blah may not be a valid value. Also, the application is written (and live) so the fewer changes the better.
So, my immediate thought was "let's override the Text property setter and check that the value is in the list". That, it turns out, is nothing like as simple as it would seem. The problem being that the "Text" property is set to all kinds of things, in all kinds of scenarios. For example, it's set when the DataSource property is assigned, or when the SelectedIndex is set to -1. Also, it's set to the string representation of the selected item - so if you happen to have a ComboBox control that's bound to a List of KeyValue pairs, you get the "Text" property set to something like "[Key,Value]". If it's bound to a DataTable/DataView, you get the string representation of the DataRow, and that gets even harder to detect.
It's at this point I thought that there might be another way to achieve the desired result (which is to detect the setting of the Text property to some invalid value - which does nothing).
Any ideas ?
Upon reflection, is this a reasonable work-around ?
/// <summary>
/// Gets or sets the text associated with this control.
/// </summary>
public override string Text
{
get { return base.Text; }
set
{
base.Text = value;
if ((value != null) && (base.Text != value))
if (value == "")
this.SelectedIndex = -1;
else
throw new ArgumentException(String.Format("Cannot set Text property of {0} to \"{1}\".", this.Name, value));
}
}

DataBinding with dependency property in wpf

I have 2 windows w1 and w2 with one textbox each and data in txtbox1 in w1 needs to be appearing in w2 txtbox .How to achieve this using dependency properties
I suggest you rather use a common DataContext for your two windows than binding them to each other. As an example, let's assume you have this class as DataModel:
public class MyDataModel : INotifyPropertyChanged
{
private string text;
public string Text {
get { return text; }
set {
text = value;
RaisePropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you can set it as DataContext on your windows:
MyDataModel model = new MyDataModel();
model.Text = "Hello World";
MyWindow a = new MyWindow();
MyOtherWindow b = new MyOtherWindow();
a.DataContext = model;
b.DataContext = model;
If you have done this, you can set the Text Property of the TextBoxes in every window to a Binding like this:
<TextBox Text="{Binding Text}"/>
Both Textboxes will now automatically update if you set model.Text to another value.
Is there a mother of the 2 forms? a form that creates both of them?
In this case what I normally do, since I'm implementing INotifyOnPropertyChanged anyway, is in the mother I subscribe to the w1's PropertyChanged event, get the value, and then place it in the property of w2.
I wouldnt really use Dependency Properties
I know its not pretty but I generally prefer it because I like to keep the 1 viewmodel to 1 view relationship. My boss thinks its easier to read and understand this way.

WPF ComboBox binding behaviour

I have the following XAML markup:
<TextBox x:Name="MyTextBox" Text="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Products}" DisplayMemberPath="ProductName"
SelectedValue="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber}"
SelectedValuePath="ProductNumber" />
My View's DataContext is bound to a viewmodel containing a public property called SelectedCustomer. Customer objects contain a FavouriteProduct property of type Product and Product objects contain public properties ProductNumber and ProductName.
The behaviour I'm looking for is to have the SelectedItem of the ComboBox update the Text in the TextBox and vice versa. ComboBox to TextBox works just fine. Selecting any product in the ComboBox updates the TextBox with the product number of that product. However when I try to go the other way I get som strange behaviour. It only works for the items that come before the selected item. I will try to explain:
Consider the following list of products ([Product Number], [Product Name]):
Fanta
Pepsi
Coca Cola
Sprite
Water
Now lets say that the SelectedCustomer's favourite product is Coca Cola (must be a developer). So when the window opens the TextBox reads 3 and the ComboBox reads Coca Cola. Lovely. Now lets change the product number in the TextBox to 2. The ComboBox updates it's value to Pepsi. Now try to change the product number in the TextBox to anything higher then the number for Coca Cola (3). Not so lovely. Selecting either 4 (Sprite) or 5 (Water) makes the ComboBox revert back to Coca Cola. So the behaviour seems to be that anything below the item that you open the window width from the list in the ItemSource does not work. Set it to 1 (Fanta) and none of the others work. Set it to 5 (Water) and they all work. Could this have to do with some initialisation for the ComboBox? Potential bug? Curious if anyone else have seen this behaviour.
UPDATE:
After reading Mike Brown's response I have created properties for SelectedProduct and SelectedProductNumber. The problem I am having with this is that as soon as you select something from the ComboBox you end up in an endless loop where the properties keep updatign each other. Have I implemented the OnPropertyChanged handler incorrectly or is there something I am missing? Here is a snippet of code from my ViewModel:
private int _SelectedProductNumber = -1;
public int SelectedProductNumber
{
get
{
if (_SelectedProductNumber == -1 && SelectedCustomer.Product != null)
_SelectedProductNumber = SelectedCustomer.Product.ProductNumber;
return _SelectedProductNumber;
}
set
{
_SelectedProductNumber = value;
OnPropertyChanged("SelectedProductNumber");
_SelectedProduct = ProductList.FirstOrDefault(s => s.ProductNumber == value);
}
}
private Product _SelectedProduct;
public Product SelectedProduct
{
get
{
if (_SelectedProduct == null)
_SelectedProduct = SelectedCustomer.Product;
return _SelectedProduct;
}
set
{
_SelectedProduct = value;
OnPropertyChanged("SelectedProduct");
_SelectedProductNumber = value.ProductNumber;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
UPDATE 2
I have changed the implementation slightly now by updating the SelectedCustomer.FavouriteProduct from both properties and then using that when reading their values. This now works but I'm not sure it's the 'correct way'.
private int _SelectedProductNumber = 0;
public int SelectedProductNumber
{
get
{
if (SelectedCustomer.Product != null)
_SelectedProductNumber = SelectedCustomer.Product.ProductNumber;
return _SelectedProductNumber;
}
set
{
_SelectedProductNumber = value;
SelectedCustomer.FavouriteProduct = ProductList.FirstOrDefault(s => s.ProductNumber == value);
OnPropertyChanged("SelectedProductNumber");
OnPropertyChanged("SelectedProduct");
}
}
private Product _SelectedProduct;
public Product SelectedProduct
{
get
{
if (SelectedCustomer.Product != null)
_SelectedProduct = SelectedCustomer.Product;
return _SelectedProduct;
}
set
{
_SelectedProduct = value;
SelectedCustomer.FavouriteProduct = value;
OnPropertyChanged("SelectedProduct");
OnPropertyChanged("SelectedProductNumber");
}
}
Your aim is not too clear so I have written the folloiwng so support either options I can see.
To keep two elements bound to one item in sync you can set the IsSynchronizedWithCurrentItem="True" on your combobox as shown below:
<TextBox x:Name="MyTextBox" Text="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Products}" DisplayMemberPath="ProductName"
SelectedValue="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber}"
IsSynchronizedWithCurrentItem="True"
SelectedValuePath="ProductNumber" />
This will mean everything in the current window bound to the same background object will keep in sync and not give the odd behaviours you are seeing.
This quote form this longer MSDN article describes the effect:
The IsSynchronizedWithCurrentItem
attribute is important in that, when
the selection changes, that is what
changes the "current item" as far as
the window is concerned. This tells
the WPF engine that this object is
going to be used to change the current
item. Without this attribute, the
current item in the DataContext won't
change, and therefore your text boxes
will assume that it is still on the
first item in the list.
Then setting the Mode=TwoWay as suggested by the other answer will only ensure that both when you update the textbox the underlying object will be updated and when you update the object the textbox is updated.
This makes the textbox edit the selected items text and not select the item in the combolist with the matching text (which is the alternative think you are may be trying to achieve?)
To achieve the synchronised selection effect it may be worth setting IsEditable="True" on the combobox to allow users to type items in and dropping the text box. Alternatively if you need two boxes replace the textbox with a second combobox with IsSynchronizedWithCurrentItem="True" and IsEditable="True" then a styled to make it like a text box.
What you want to do is expose separate properties on your ViewModel for the currently selected product and currently selected product number. When the selected product is changed, update the product number and vice versa. So your viewmodel should look something like this
public class MyViewModel:INotifyPropertyChanged
{
private Product _SelectedProduct;
public Product SelectedProduct
{
get { return _SelectedProduct; }
set
{
_SelectedProduct = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProduct"));
_SelectedProductID = _SelectedProduct.ID;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProductID"));
}
}
private int _SelectedProductID;
public int SelectedProductID
{
get { return _SelectedProductID; }
set
{
_SelectedProductID = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProductID"));
_SelectedProduct = _AvailableProducts.FirstOrDefault(p => p.ID == value);
PropertyChanged(this,new PropertyChangedEventArgs("SelectedProduct"));
}
}
private IEnumerable<Product> _AvailableProducts = GetAvailableProducts();
private static IEnumerable<Product> GetAvailableProducts()
{
return new List<Product>
{
new Product{ID=1, ProductName = "Coke"},
new Product{ID = 2, ProductName="Sprite"},
new Product{ID = 3, ProductName = "Vault"},
new Product{ID=4, ProductName = "Barq's"}
};
}
public IEnumerable<Product> AvailableProducts
{
get { return _AvailableProducts; }
}
private Customer _SelectedCustomer;
public Customer SelectedCustomer
{
get { return _SelectedCustomer; }
set
{
_SelectedCustomer = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedCustomer"));
SelectedProduct = value.FavoriteProduct;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So now your XAML binds to the appropriate properties and the viewModel is responsible for syncrhronization
<TextBox
x:Name="MyTextBox"
Text="{Binding Path=SelectedProductID, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox
x:Name="MyComboBox"
ItemsSource="{Binding AvailableProducts}"
DisplayMemberPath="ProductName"
SelectedItem="{Binding SelectedProduct}" />
Don't forget to implement the rest of INotifyPropertyChanged and the GetAvailableProducts function. Also there may be some errors. I hand typed this here instead of using VS but you should get the general idea.
Try:
SelectedItem="{Binding Path=YourPath, Mode=TwoWay"}
instead of setting SelectedValue and SelectedValuePath.
Might work with SelectedValue too, don't forget the Mode=TwoWay, since this isn't the default.
A good approuch would to use the master detail pattern - bind the master (the items view, e.g. combobox) to the data source collection and the detail view (e.g. text box) to the selected item in the source collection, using a binding converter to read/write the appropriate property.
Here is an example:
http://blogs.microsoft.co.il/blogs/tomershamam/archive/2008/03/28/63397.aspx
Notice the master binding is of the form {Binding} or {Binding SourceCollection} and the details binding is of the form {Binding } or {Binding SourceCollection}.
To get this working you need to wrap you collection with an object that keeps the selected item. WPF has one of these built-in: ObjectDataProvider.
Example:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/068977c9-95a8-4b4a-9d38-b0cc36d06446

Resources