TextBox not updating properly when decimal point is input - wpf

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();
}
}
}
}

Related

Setting a string to be empty if value is null

I am not sure how to search for the issue I am trying to solve here. In the program I am writing (in VB.Net) I am trying to assign values pulled from a database to different variables in a structure.
Now my issue is that sometimes, some of the values pulled from the database are NULL, for example not every phone number has an extension. This is what I have for my code at the moment:
Structure CustomerContact
Public _name As String
Public _email As String
Public _fax As String
Public _phone1 As String
Public _phone2 As String
Public _phone3 As String
Public _ext1 As String
Public _ext2 As String
Public _ext3 As String
Public _type1 As String
Public _type2 As String
Public _type3 As String
End Structure
Dim contactData As DataTable = CustomerDBFunctions.GetCustomerContacts(Customer)
For Each row As DataRow In contactData.Rows
If contacts.Count < 1 Then
contacts.Add(New CustomerContact With {
._name = row.Item("FullName").ToString() & " (" & row.Item("ContactType").ToString() & ")",
._email = row.Item("Email").ToString(),
._fax = row.Item("Fax").ToString(),
._phone1 = row.Item("Phone").ToString(),
._ext1 = row.Item("Extension").ToString(),
._type1 = row.Item("PhoneType").ToString()})
End If
Next
Right now I am getting an error when the value in the database is NULL because it can't assign a NULL value to a string. I'd like to in the instances where a NULL value is present instead set the value of the variable to "" instead. I am just unsure how to code this.
Technically, the problem isn't that the column is null. String is a reference type, so it can but null (though, if it was null, you wouldn't be able to call ToString on it anyway). What's actually going on is that ADO.NET always returns DBNull.Value for all columns where the row contains a null value.
You could check it, like this:
If row.Item("Phone") <> DBNull.Value Then
customerContact._phone1 = row.Item("Phone")?.ToString()
End If
Note, I used ?. instead of . when calling ToString just in case the column actually is null rather than DbNull.Value. The reason for this is that I don't know what kind of code you're using to fill that DataTable. If it's ADO.NET that's filling it, it'll never be null, but if it's custom code that populates it via some other means, it might get actual nulls in it.
Since you are using a DataTable to return the value, it has a convenient IsNull method that you can use, which cleans up the code a little bit:
If Not row.IsNull("Phone") Then
customerContact._phone1 = row.Item("Phone")?.ToString()
End If
Obviously, if you're doing this a lot, it would be good to wrap it up into a reusable function. Also, if you want to shorten it up into one line, you could do so like this:
._phone1 = If(row.IsNull("Phone"), row.Item("Phone")?.ToString(), Nothing)
String concatenation can be used to convert Nothing to "" (other alternatives in my answer here)
._fax = row!Fax & "",

WPF Text Box allowing decimal entries

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/

Error message in a property

This is my problem. If a user type a number in the textbox is all right, but if he type a char, I don't see the messagebox () in the property.
Why ?
<TextBox HorizontalAlignment="Left"
TabIndex="12"
Text="{Binding Time_HH, UpdateSourceTrigger=PropertyChanged,StringFormat='{}{##}'}"
FlowDirection="RightToLeft"
MaxLength ="2"
Height="30"
Width="30" />
And this is the property
Private _Time_HH As Integer
Public Property Time_HH() As Integer
Get
Return _Time_HH
End Get
Set(value As Integer)
For i = 0 To Len(value.ToString)
If IsNumeric(value.ToString(i)) = False Then
MessageBox.Show("Error")
value = 0
End If
Next
_Time_HH = value
OnPropertyChanged("Time_HH")
End Set
End Property
Your Time_HH property is an Integer, there's no way it's gonna contain a non-numeric character.
At most, what will happen is that your Binding will fail due to the type mismatch (is your TextBox showing a red outline?)
If you wanna check if your user enters non-numeric characters, you have to use a type that allows so: a String.
Try this:
Private _Time_HH As Integer
Public Property Time_HH() As String
Get
Return _Time_HH.ToString()
End Get
Set(value As String)
For i = 0 To Len(value)
If IsNumeric(value.ToString(i)) = False Then
MessageBox.Show("Error")
value = 0
End If
Next
_Time_HH = Integer.Parse(value)
OnPropertyChanged("Time_HH")
End Set
End Property
If you need to use the numeric value, use the Integer field. You could create a second property of type Integer that simply exposes the field, if you want to use it for another Binding or something like that (remember to raise the OnPropertyChanged for that property too, then, on the Time_HH setter)
CAUTION - The code above will raise an Exception if the user types something like "00,01-2,0". IsNumeric returns True for all the characters in that String, but that doesn't mean it is a correct number.
In my opinion, it would be better to do this:
Private _Time_HH As Integer
Public Property Time_HH() As String
Get
Return _Time_HH.ToString()
End Get
Set(value As String)
Dim int As Integer
If Integer.TryParse(value, int) = False Then
MessageBox.Show("Error")
End If
_Time_HH = int
OnPropertyChanged("Time_HH")
End Set
End Property
Sorry if I did some mistakes, I usually code in C# and my VB is pretty rusty :P

Label with different font sizes

Basically I want to achieve something like this:
but I have no idea how to do it, I tried with 2 labels to combine them but the result isn't that great..
You need to draw the text from a different point of view, namely, the baseline:
Public Class MyLabel
Inherits Label
<Browsable(False)> _
Public Overrides Property AutoSize As Boolean
Get
Return False
End Get
Set(value As Boolean)
'MyBase.AutoSize = value
End Set
End Property
Protected Overrides Sub OnPaint(e As PaintEventArgs)
'MyBase.OnPaint(e)
Dim fromLine As Integer = Me.ClientSize.Height * 0.75
Dim g As Graphics = e.Graphics
Dim fontParts() As String = Me.Text.Split(".")
Using bigFont As New Font(Me.Font.FontFamily, 20)
TextRenderer.DrawText(g, fontParts(0), bigFont, _
New Point(0, fromLine - GetBaseLine(bigFont, g)), _
Me.ForeColor, Color.Empty)
If fontParts.Length > 1 Then
Dim bigWidth As Integer = TextRenderer.MeasureText(g, fontParts(0), bigFont, _
Point.Empty, TextFormatFlags.NoPadding).Width
Using smallFont As New Font(Me.Font.FontFamily, 8)
TextRenderer.DrawText(g, "." & fontParts(1), smallFont, _
New Point(bigWidth + 3, fromLine - GetBaseLine(smallFont, g)), _
Me.ForeColor, Color.Empty)
End Using
End If
End Using
End Sub
Private Function GetBaseLine(fromFont As Font, g As Graphics) As Single
Dim fontHeight As Single = fromFont.GetHeight(g)
Dim lineSpacing As Single = fromFont.FontFamily.GetLineSpacing(fromFont.Style)
Dim cellAscent As Single = fromFont.FontFamily.GetCellAscent(fromFont.Style)
Return fontHeight * cellAscent / lineSpacing
End Function
End Class
The code basically measures the height of the font from a line. In my example, I used the bottom 25% of the Label's client space to say, start drawing from this line: Me.ClientSize.Height * 0.75.
For each font you use, you would have to measure that font's baseline and subtract that from your drawing line in order to offset your drawing position of the text.
Measuring an individual character's dimensions is not easy due to aliasing and glyph overhangs. I added a small padding between the big text and the small text: bigWidth + 3 to try to make it look good. If the big number ends in a 7, the distance looks a little off because the stem of the 7 is angled.
Result:
Create a new class inherited from Label, and override the void OnPaint(PaintEventArgs e) method to change the default rendering behavior:
public class MyLabel : Label
{
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawString("A", Font, new SolidBrush(ForeColor), 10, 10);
e.Graphics.DrawString("B", new Font(Font.FontFamily, 20), new SolidBrush(ForeColor), 50, 10);
}
}
As a result, "B" will be two times larger the "A". You can achieve your goal in the same way, but you have to calculate the position of your sub-strings ("145", ".", "54") and draw them.
Use devexpress LabelControl.AllowHtmlString property to true and use the supported <size> tag within the LabelControl's Text property as detailed in the HTML Text Formatting documentation.
you can use user control WPF in windows form. to do that do this step.
1. add user control to the windows form
2.from xml of usercontrol name grid like t1
3. add this function to the usercontrol.wpf.cs
public void Actor(string text)
{
StringBuilder sb = new StringBuilder();
sb.Append(#"<TextBlock xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'> ");
sb.Append(text);
sb.Append(#"</TextBlock>");
TextBlock myButton = (TextBlock)XamlReader.Parse(sb.ToString());
this.t1.Children.Clear();
t1.Children.Add(myButton);
}
4. after that from form1.css add this function in every where you want.
userControl11.Actor("<Run Text='Hi ' FontWeight='Bold'/><Run Text='Hitler ' FontWeight='Bold'/>");
userControl11.Actor(" < Run FontWeight = 'Bold' FontSize = '14' Text = 'This is WPF TextBlock Example. ' />");
you can mange the write code "" of Actor function by using xml wpf.

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.

Resources