Winforms (Horizontal) Scrollbar Unexplained Behavior - winforms

When I resize my form with the resize handle on the bottom right corner of the form, it fires the sizechanged event of my user control. Inside the user control I have code to set the maximum and value of the horizontal scroll bar.
If I resize to the right, the code works as expected. When I resize to the left, the scrolling thumb gets really big and scrolling does not work as expected. It's as if the Maximum got set to a low number, but my Debug.WriteLine shows that this is not the case. Actually, as I slowly resize the form to be narrower, it quickly toggles between doing it right and doing it wrong.
I deal with scroll bars rarely and when I do they are always a pain. Is there a ScrollBar guru who knows why this is happening? I googled it and searched SO too, but I really don't know what to search on.
Here is my code. The relevant part is what is called from the sizechanged event handler, which is almost to the bottom of the code.
Imports System.Reflection
Public Class Grid
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.UserPaint Or ControlStyles.DoubleBuffer, True) 'Or ControlStyles.ResizeRedraw
End Sub
Private _FirstVisibleRow As Row
Private Property FirstVisibleRow As Row
Get
If _FirstVisibleRow Is Nothing And Rows.Any Then
_FirstVisibleRow = Rows.First
End If
Return _FirstVisibleRow
End Get
Set(value As Row)
_FirstVisibleRow = value
LastVisibleRow = GetLastVisibleRow()
End Set
End Property
Private _HeaderRow As Row
Public Property HeaderRow As Row
Get
If _HeaderRow Is Nothing Then
_HeaderRow = New Row(Me, RowHeight)
For Each Column As Column In Columns
_HeaderRow.Cells.Add(New Cell(Column, _HeaderRow))
Next
End If
Return _HeaderRow
End Get
Private Set(value As Row)
_HeaderRow = value
End Set
End Property
Private _LastVisibleRow As Row
Private Property LastVisibleRow As Row
Get
If _LastVisibleRow Is Nothing Then
_LastVisibleRow = GetLastVisibleRow()
End If
Return _LastVisibleRow
End Get
Set(value As Row)
_LastVisibleRow = value
End Set
End Property
Private _TotalColumnWidth As Integer
Friend Property TotalColumnWidth As Integer
Get
If _TotalColumnWidth = Nothing Then
_TotalColumnWidth = GetTotalColumnWidth()
End If
Return _TotalColumnWidth
End Get
Set(value As Integer)
_TotalColumnWidth = value
SetScrollBarVisibility()
End Set
End Property
Private _TotalRowHeight As Integer
Friend Property TotalRowHeight As Integer
Get
If _TotalRowHeight = Nothing Then
_TotalRowHeight = GetTotalRowHeight()
End If
Return _TotalRowHeight
End Get
Set(value As Integer)
_TotalRowHeight = value
SetScrollBarVisibility()
End Set
End Property
Private _VisibleGridSize As Size
Private Property VisibleGridSize As Size
Get
If _VisibleGridSize = Nothing Then
_VisibleGridSize = GetVisibleGridSize()
End If
Return _VisibleGridSize
End Get
Set(value As Size)
_VisibleGridSize = value
VisibleRowCount = GetVisibleRowCount()
SetScrollBarVisibility()
End Set
End Property
Private Sub SetScrollBarVisibility()
VScrollBar1.Bounds = New Rectangle(Width - VScrollBar1.Width, 0, VScrollBar1.Width, Height - IIf(HScrollBar1.Visible, HScrollBar1.Height, 0))
HScrollBar1.Bounds = New Rectangle(0, Height - HScrollBar1.Height, Width - IIf(VScrollBar1.Visible, VScrollBar1.Width, 0), HScrollBar1.Height)
VScrollBar1.Maximum = Math.Max(0, TotalRowHeight - Height - IIf(HScrollBar1.Visible, HScrollBar1.Height, 0))
HScrollBar1.Maximum = Math.Max(0, TotalColumnWidth - Width + IIf(VScrollBar1.Visible, VScrollBar1.Width, 0))
HScrollBar1.Value = 0
VScrollBar1.Visible = TotalRowHeight > VisibleGridSize.Height
HScrollBar1.Visible = TotalColumnWidth > VisibleGridSize.Width
Debug.WriteLine(String.Format("HScrollBar1.Minimum {0}, HScrollBar1.Maximum {1}, HScrollBar1.Value {2}", HScrollBar1.Minimum, HScrollBar1.Maximum, HScrollBar1.Value))
End Sub
Private _VisibleRowCount As Integer
Private Property VisibleRowCount As Integer
Get
If _VisibleRowCount = 0 Then
_VisibleRowCount = GetVisibleRowCount()
End If
Return _VisibleRowCount
End Get
Set(value As Integer)
_VisibleRowCount = value
LastVisibleRow = GetLastVisibleRow()
PageHeight = GetPageHeight()
End Set
End Property
Private Function GetLastVisibleRow() As Row
If Not Rows.Any Then Return Nothing
Return Rows(Math.Min(FirstVisibleRow.Index + VisibleRowCount - 1, Rows.Count - 1))
End Function
Private Function GetPageHeight() As Integer
Return RowHeight * GetVisibleRowCount()
End Function
Private Function GetRowHeight() As Integer
Return TextRenderer.MeasureText("X", Font).Height + 6
End Function
Private Function GetVisibleGridSize() As Size
Return New Size(Width - IIf(VScrollBar1.Visible, VScrollBar1.Width, 0),
Height - HeaderRow.Height - IIf(HScrollBar1.Visible, HScrollBar1.Height, 0)) 'don't count header row or horiz scroll bar
End Function
Private Function GetVisibleRowCount() As Integer
Return Math.Ceiling(VisibleGridSize.Height / RowHeight)
End Function
Public Shadows Sub Refresh()
ClearSelection()
_HeaderRow = Nothing
_Rows = Nothing
AutoSizeColumns()
TotalRowHeight = GetTotalRowHeight()
Invalidate()
End Sub
Friend Function GetTotalColumnWidth() As Integer
Return Columns.Select(Function(x) x.Width).Aggregate(0, Function(x, y) x + y)
End Function
Friend Function GetTotalRowHeight() As Integer
Return Rows.Select(Function(x) x.Height).Aggregate(0, Function(x, y) x + y)
End Function
Private Function VisibleRows() As List(Of Row)
Return Rows.Where(Function(x) x.Index >= FirstVisibleRow.Index AndAlso x.Index <= LastVisibleRow.Index).ToList
End Function
Private Sub Grid_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
e.Graphics.Clear(BackColor)
'e.ClipRectangle
Dim Left As Integer = -HScrollBar1.Value
For Each Column As Column In Columns
Left = Column.Draw(e.Graphics, Left)
Next
Dim Top As Integer = HeaderRow.Draw(e.Graphics)
For Each Row As Row In VisibleRows()
Top = Row.Draw(e.Graphics, Top)
Next
End Sub
Private Sub Grid_Scroll(sender As Object, e As System.Windows.Forms.ScrollEventArgs) Handles Me.Scroll
If e.ScrollOrientation = ScrollOrientation.VerticalScroll Then
Select Case e.Type
Case ScrollEventType.First
FirstVisibleRow = Rows.First
Case ScrollEventType.Last
FirstVisibleRow = Rows(Rows.Last.Index - VisibleRowCount + 1)
Case ScrollEventType.SmallDecrement
FirstVisibleRow = Rows(Math.Max(FirstVisibleRow.Index - 1, 0))
Case ScrollEventType.SmallIncrement
FirstVisibleRow = Rows(Math.Min(FirstVisibleRow.Index + 1, Rows.Last.Index - VisibleRowCount + 1))
Case ScrollEventType.LargeDecrement
FirstVisibleRow = Rows(Math.Max(FirstVisibleRow.Index - VisibleRowCount, 0))
Case ScrollEventType.LargeIncrement
FirstVisibleRow = Rows(Math.Min(LastVisibleRow.Index, Rows.Last.Index - VisibleRowCount + 1))
End Select
End If
Invalidate()
End Sub
Private Sub Grid_SizeChanged(sender As Object, e As System.EventArgs) Handles Me.SizeChanged
VisibleGridSize = GetVisibleGridSize()
End Sub
Private Sub HScrollBar1_Scroll(sender As Object, e As System.Windows.Forms.ScrollEventArgs) Handles HScrollBar1.Scroll
Invalidate()
End Sub
End Class

The AutoScroll property of my UserControl was getting set to True by a parent control. Once I set it back to False in the designer, my code works as expected 100% of the time.

Related

Implementing a custom column sorting

I'm having issues implementing a custom sort on a datagrid column. Could someone explain what I'm doing wrong?
I'm trying to create a custom sort for column so that the strings are sorted by length first, then by their values. It reaches my custom made function, hits the "Throws not implemented line", and then gives me an InvalidOperationException error.
Private Sub customSorting(sender As Object, e As DataGridSortingEventArgs) Handles grid.Sorting
If e.Column.Header = "Test" Then
Dim comp As IComparer = New RowComparer(e.Column.SortDirection)
Dim lcv As ListCollectionView = CollectionViewSource.GetDefaultView(grid.ItemsSource)
lcv.CustomSort = comp
e.Handled = True
End If
End Sub
Private Class RowComparer
Implements System.Collections.IComparer
Private o As Integer = 0
Public Sub New(order As Integer)
o = order
End Sub
Public Function Compare(x As Object, y As Object) As Integer Implements IComparer.Compare
Throw New NotImplementedException()
If x.ToString.Length > y.ToString.Length Then
If o = 0 Then
Return 1
Else
Return -1
End If
ElseIf x.ToString.Length < y.ToString.Length Then
If o = 0 Then
Return -1
Else
Return 1
End If
Else
Return String.Compare(x, y)
End If
End Function
End Class
Visual studios automatically generated the 'Throw New NotImplementedException()' which was stopping my program.

vb.net class and array

I am having an issue with trying to take info from class and putting them into an array with the class data type. I am getting a null error. I can see its not adding the variable info into the array at all. I am unsure what it is I am missing. Can anyone point out to me what it is?
Here is the code:
Option Explicit On
Option Strict On
Option Infer Off
Public Class Form1
Public Class Chandelier
Private _strFinish As String
Private _intLights As Integer
Private _blnCrystal As Boolean
Private _dblTotal As Double
Public Property Finish As String
Get
Return _strFinish
End Get
Set(value As String)
_strFinish = value
End Set
End Property
Public Property Lights As Integer
Get
Return _intLights
End Get
Set(value As Integer)
If value > 0 Then
_intLights = value
Else
_intLights = 0
End If
End Set
End Property
Public Property Crystal As Boolean
Get
Return _blnCrystal
End Get
Set(value As Boolean)
_blnCrystal = value
End Set
End Property
Public Property Total As Double
Get
Return _dblTotal
End Get
Set(value As Double)
If value > 0 Then
_dblTotal = value
Else
_dblTotal = 0
End If
End Set
End Property
Public Sub New()
_strFinish = Nothing
_intLights = 0
_blnCrystal = False
_dblTotal = 0
End Sub
Public Function getCrystal() As Boolean
Return _blnCrystal
End Function
Public Function getPrice() As Double
Dim crystalPrice As Double
Dim price As Double
If _strFinish.Contains("Silver") Then
price = 39.99
If getCrystal() = True Then
crystalPrice = _intLights * 25.5
Else
crystalPrice = 0
End If
price = price + crystalPrice
End If
If _strFinish.Contains("Brass") Then
price = 49.99
If getCrystal() = True Then
crystalPrice = _intLights * 25.5
Else
crystalPrice = 0
End If
price = price + crystalPrice
End If
Return price
End Function
End Class
Public chandelierStyle(49) As Chandelier
Dim count As Integer = 0
Private Sub btnSubmit_Click(sender As Object, e As EventArgs) Handles btnSubmit.Click
Dim ceilingOrnament As New Chandelier
ceilingOrnament.Finish = cboFinish.SelectedItem.ToString
Integer.TryParse(cboLights.SelectedItem.ToString, ceilingOrnament.Lights)
If chkTrimmings.Checked Then
ceilingOrnament.Crystal = True
End If
Dim dblTotal As Double = ceilingOrnament.getPrice()
ceilingOrnament.Total = dblTotal
If count <= 49 Then
'here is where the error starts
chandelierStyle(count).Finish = ceilingOrnament.Finish
chandelierStyle(count).Lights = ceilingOrnament.Lights
chandelierStyle(count).Crystal = ceilingOrnament.Crystal
chandelierStyle(count).Total = ceilingOrnament.Total
count += 1
End If
End Sub
Just place your dynamic instance into the array, instead of trying to copy each individual field.
Change this:
If count <= 49 Then
'here is where the error starts
chandelierStyle(count).Finish = ceilingOrnament.Finish
chandelierStyle(count).Lights = ceilingOrnament.Lights
chandelierStyle(count).Crystal = ceilingOrnament.Crystal
chandelierStyle(count).Total = ceilingOrnament.Total
count += 1
End If
To:
If count <= 49 Then
'here is where the error starts
chandelierStyle(count) = ceilingOrnament
count += 1
End If
Looks like you create your array of 49 elements with this line:
Public chandelierStyle(49) As Chandelier
but you never actually initialize its contents. What you have is 49 empty slots, not 49 Chandelier instances. You need to first set each array slot to a new instance of Chandelier before you can update their properties in your button Click event.
You can use a For loop to initialize each array slot before you use it; something like:
For i As Integer = 0 To 49
chandelierStyle(i) = New Chandelier()
Next i
You'd have this loop in the constructor of the class (looks like it's a Form) that contains your chandelierStyle array.
Note: Don't quote me on that. :) I haven't used VB.NET for ages and there may be an easier way to do this.
Edit: Or, you can do what Idle_Mind suggested in his answer and just stash the ceilingOrnament instance in the appropriate array slot.

Anchoring a Form in vb.net 2010

my question is how to anchor a child mdi to its parent mdi so that each time the user expands or minimizes the parent form the child form automatically follows. I already tried to use the resize function in form but it doesn't help
thanks all help is appriciated
See below - a quick version I wrote, which seems to handle most anchoring scenarios. You may need to polish this code a bit. But this should get you started:
Imports System.ComponentModel
Public Class MDIChildForm
Dim p_eMyAnchor As AnchorStyles
Dim p_mdiParent As Form
Dim p_iOldHeight, p_iOldWidth As Integer
<DefaultValue(AnchorStyles.Left Or AnchorStyles.Top)>
Public Property MyAnchor As AnchorStyles
Get
Return p_eMyAnchor
End Get
Set(value As AnchorStyles)
p_eMyAnchor = value
chkAnchorTop.Checked = (p_eMyAnchor And AnchorStyles.Top)
chkAnchorLeft.Checked = (p_eMyAnchor And AnchorStyles.Left)
chkAnchorRight.Checked = (p_eMyAnchor And AnchorStyles.Right)
chkAnchorBottom.Checked = (p_eMyAnchor And AnchorStyles.Bottom)
End Set
End Property
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
p_eMyAnchor = AnchorStyles.Left Or AnchorStyles.Top
End Sub
Public Sub ShowAsMDIChild(mdiParent As Form)
p_mdiParent = mdiParent
Me.MdiParent = mdiParent
AddHandler mdiParent.ResizeBegin, AddressOf MDIParentForm_ResizeBegin
AddHandler mdiParent.ResizeEnd, AddressOf MDIParentForm_ResizeEnd
Me.Show()
End Sub
Private Sub MDIParentForm_ResizeBegin(sender As Object, e As EventArgs)
Dim frm As Form = DirectCast(sender, Form)
p_iOldWidth = frm.Width
p_iOldHeight = frm.Height
End Sub
Private Sub MDIParentForm_ResizeEnd(sender As Object, e As EventArgs)
Dim parentForm As Form = DirectCast(sender, Form)
'handling for horizontal anchoring
Dim deltaWidth As Integer = parentForm.Width - p_iOldWidth
Dim fAnchorLeft As Boolean = p_eMyAnchor And AnchorStyles.Left
Dim fAnchorRight As Boolean = p_eMyAnchor And AnchorStyles.Right
Select Case fAnchorLeft
Case True : If fAnchorRight Then Me.Width += deltaWidth
Case False
Dim coef As Single = If(fAnchorRight, 1, 0.5)
Me.Left += deltaWidth * coef
End Select
'handling for vertical anchoring
Dim deltaHeight As Integer = parentForm.Height - p_iOldHeight
Dim fAnchorTop As Boolean = p_eMyAnchor And AnchorStyles.Top
Dim fAnchorBottom As Boolean = p_eMyAnchor And AnchorStyles.Bottom
Select Case fAnchorTop
Case True : If fAnchorBottom Then Me.Height += deltaHeight
Case False
Dim coef As Single = If(fAnchorBottom, 1, 0.5)
Me.Top += deltaHeight * coef
End Select
End Sub
End Class
For horizonal anchoring, the following rules are used:
left - nothing happens (default behavior)
left and right - expand width to parent form width delta
right - move left by parent form width delta
no anchor - move left by half parent form width delta.
Same principle applies to vertical anchoring, for top and bottom respectively.
You can get the full project to play with here (Mediafire).
try this:
Me.Anchor = AnchorStyles.Bottom
Me.Anchor = AnchorStyles.Left
Me.Anchor = AnchorStyles.Right
Me.Anchor = AnchorStyles.Top

Binding to a WPF custom control dependency property

I am having a hard time binding a property of a wpf custom control.
Here is an excerp of my xaml custom datagrid cell whose datacontext is the list(of date):
<DataGridTemplateColumn Header="Start" MinWidth="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<timePicker:CustomTimePicker selectedTime="{Binding Path=startDate}" MinWidth="100" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
As you can see, I try to bind the startDate Property of my List(Of Date) to the selectedTime dependency property in my custom wpf control TimePicker.
Here is the definition of the customtimepicker class with the selectedDate dependency-property:
Public Class CustomTimePicker
Private _hours As String
Private _minutes As String
Private _hoursChoices As List(Of String)
Private _minutesChoices As List(Of String)
Public Shared selectedTimeProperty As DependencyProperty = DependencyProperty.Register("selectedTime", GetType(Date), GetType(CustomTimePicker), New FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, New PropertyChangedCallback(AddressOf CustomTimePicker.OnSelectedTimeChanged)))
Public Property selectedTime() As Date
Get
Return DirectCast(GetValue(selectedTimeProperty), Date)
End Get
Set(value As Date)
SetValue(selectedTimeProperty, value)
End Set
End Property
Public Property hours As String
Get
Return _hours
End Get
Set(value As String)
_hours = value
End Set
End Property
Public Property minutes As String
Get
Return _minutes
End Get
Set(value As String)
_minutes = value
End Set
End Property
Public Property hoursChoices As List(Of String)
Get
Return _hoursChoices
End Get
Set(value As List(Of String))
_hoursChoices = value
End Set
End Property
Public Property minutesChoices As List(Of String)
Get
Return _minutesChoices
End Get
Set(value As List(Of String))
_minutesChoices = value
End Set
End Property
Protected Shared Sub OnSelectedTimeChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
Console.WriteLine("#######")
Console.WriteLine(CType(obj, CustomTimePicker).selectedTime.ToString)
End Sub
Protected Shared Sub OnCoerce()
End Sub
Protected Sub OnSelectedHourChanged(obj As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles hourComboBox.SelectionChanged
Dim minute As Integer = 0
Dim result As Date = New Date(2000, 1, 1, hourComboBox.SelectedIndex, selectedTime.Minute, 0)
selectedTime = result
Console.WriteLine("---")
Console.WriteLine(result.ToString)
Console.WriteLine(selectedTime.ToString)
End Sub
Protected Sub OnSelectedMinuteChanged(obj As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles minutesComboBox.SelectionChanged
Dim minute As Integer = 0
Console.WriteLine("bbbbbb")
Console.WriteLine(selectedTime.ToString)
Select Case minutesComboBox.SelectedIndex
Case 0
minute = 0
Case 1
minute = 15
Case 2
minute = 30
Case 3
minute = 45
End Select
Dim result As Date = New Date(2000, 1, 1, selectedTime.Hour, minute, 0)
selectedTime = result
End Sub
Protected Overrides Sub OnInitialized(e As System.EventArgs)
MyBase.OnInitialized(e)
_hoursChoices = New List(Of String)
_minutesChoices = New List(Of String)
Console.WriteLine("aaaaaaa")
Console.WriteLine(selectedTime.ToString)
For i As Integer = 0 To 23
_hoursChoices.Add(i.ToString)
Next
_minutesChoices.Add("00")
_minutesChoices.Add("15")
_minutesChoices.Add("30")
_minutesChoices.Add("45")
hourComboBox.ItemsSource = hoursChoices
hourComboBox.DisplayMemberPath = hours
hourComboBox.SelectedValuePath = hours
hourComboBox.SelectedValue = hours
minutesComboBox.ItemsSource = minutesChoices
minutesComboBox.DisplayMemberPath = minutes
minutesComboBox.SelectedValuePath = minutes
minutesComboBox.SelectedValue = minutes
Select Case selectedTime.Minute
Case 0 To 14
minutesComboBox.SelectedIndex = 0
Case 15 To 29
minutesComboBox.SelectedIndex = 1
Case 30 To 44
minutesComboBox.SelectedIndex = 2
Case 45 To 59
minutesComboBox.SelectedIndex = 3
End Select
hourComboBox.SelectedIndex = selectedTime.Hour
End Sub
End Class
If I change the selectedTime property for example by
selectedTime = New Date(2000,1,1,23,59,0)
the value does not change in the datagrid. It is the same problem the other way around. I do not get the startTime values to the wpf control.
It really seems like the binding is not working, even though the application compiles and runs error free.
Can somebody help me?
Kind regards

Wpf AttachedBehavior losing base textbox functionalty when using it

I have a attached behavior issue. When I attach the behavior to my textbox I lose all my base functionality like the Max length? Here is how I am attaching it in my xaml. When I take off the attached behavior the max length work when I put it back on it doesnt work? Any help would be greatly appreciated!
This is the class I am using
Imports System.Windows
Imports System.Windows.Controls
Imports System.Globalization
Namespace AttachedBehaviours
Public Class TextBoxMaskBehavior
Inherits DependencyObject
Region "MinimumValue Property"
Public Shared Function GetMinimumValue(ByVal obj As DependencyObject) As Double
Return CDbl(obj.GetValue(MinimumValueProperty))
End Function
Public Shared Sub SetMinimumValue(ByVal obj As DependencyObject, ByVal value As Double)
obj.SetValue(MinimumValueProperty, value)
End Sub
Public Shared ReadOnly MinimumValueProperty As DependencyProperty = DependencyProperty.RegisterAttached("MinimumValue", GetType(Double), GetType(TextBoxMaskBehavior), New FrameworkPropertyMetadata(Double.NaN, AddressOf MinimumValueChangedCallback))
Private Shared Sub MinimumValueChangedCallback(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim _this As TextBox = TryCast(d, TextBox)
ValidateTextBox(_this)
End Sub
End Region
Region "MaximumValue Property"
Public Shared Function GetMaximumValue(ByVal obj As DependencyObject) As Double
Return CDbl(obj.GetValue(MaximumValueProperty))
End Function
Public Shared Sub SetMaximumValue(ByVal obj As DependencyObject, ByVal value As Double)
obj.SetValue(MaximumValueProperty, value)
End Sub
Public Shared ReadOnly MaximumValueProperty As DependencyProperty = DependencyProperty.RegisterAttached("MaximumValue", GetType(Double), GetType(TextBoxMaskBehavior), New FrameworkPropertyMetadata(Double.NaN, AddressOf MaximumValueChangedCallback))
Private Shared Sub MaximumValueChangedCallback(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim _this As TextBox = TryCast(d, TextBox)
ValidateTextBox(_this)
End Sub
End Region
Region "Mask Property"
Public Shared Function GetMask(ByVal obj As DependencyObject) As MaskType
Return CType(obj.GetValue(MaskProperty), MaskType)
End Function
Public Shared Sub SetMask(ByVal obj As DependencyObject, ByVal value As MaskType)
obj.SetValue(MaskProperty, value)
End Sub
Public Shared ReadOnly MaskProperty As DependencyProperty = DependencyProperty.RegisterAttached("Mask", GetType(MaskType), GetType(TextBoxMaskBehavior), New FrameworkPropertyMetadata(AddressOf MaskChangedCallback))
Private Shared Sub MaskChangedCallback(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If TypeOf e.OldValue Is TextBox Then
RemoveHandler TryCast(e.OldValue, TextBox).PreviewTextInput, AddressOf TextBox_PreviewTextInput
DataObject.RemovePastingHandler(TryCast(e.OldValue, TextBox), DirectCast(AddressOf TextBoxPastingEventHandler, DataObjectPastingEventHandler))
End If
Dim _this As TextBox = TryCast(d, TextBox)
If _this Is Nothing Then
Return
End If
If CType(e.NewValue, MaskType) <> MaskType.Any Then
AddHandler _this.PreviewTextInput, AddressOf TextBox_PreviewTextInput
DataObject.AddPastingHandler(_this, DirectCast(AddressOf TextBoxPastingEventHandler, DataObjectPastingEventHandler))
End If
ValidateTextBox(_this)
End Sub
End Region
Region "Private Static Methods"
Private Shared Sub ValidateTextBox(ByVal _this As TextBox)
If GetMask(_this) <> MaskType.Any Then
_this.Text = ValidateValue(GetMask(_this), _this.Text, GetMinimumValue(_this), GetMaximumValue(_this))
End If
End Sub
Private Shared Sub TextBoxPastingEventHandler(ByVal sender As Object, ByVal e As DataObjectPastingEventArgs)
Dim _this As TextBox = TryCast(sender, TextBox)
Dim clipboard As String = TryCast(e.DataObject.GetData(GetType(String)), String)
clipboard = ValidateValue(GetMask(_this), clipboard, GetMinimumValue(_this), GetMaximumValue(_this))
If Not String.IsNullOrEmpty(clipboard) Then
_this.Text = clipboard
End If
e.CancelCommand()
e.Handled = True
End Sub
Private Shared Sub TextBox_PreviewTextInput(ByVal sender As Object, ByVal e As System.Windows.Input.TextCompositionEventArgs)
Dim _this As TextBox = TryCast(sender, TextBox)
Dim isValid As Boolean = IsSymbolValid(GetMask(_this), e.Text)
e.Handled = Not isValid
If isValid Then
Dim caret As Integer = _this.CaretIndex
Dim text As String = _this.Text
Dim textInserted As Boolean = False
Dim selectionLength As Integer = 0
If _this.SelectionLength > 0 Then
text = text.Substring(0, _this.SelectionStart) & text.Substring(_this.SelectionStart + _this.SelectionLength)
caret = _this.SelectionStart
End If
If e.Text = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator Then
While True
Dim ind As Integer = text.IndexOf(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
If ind = -1 Then
Exit While
End If
text = text.Substring(0, ind) & text.Substring(ind + 1)
If caret > ind Then
caret -= 1
End If
End While
If caret = 0 Then
text = "0" & text
caret += 1
Else
If caret = 1 AndAlso String.Empty & text(0) = NumberFormatInfo.CurrentInfo.NegativeSign Then
text = NumberFormatInfo.CurrentInfo.NegativeSign & "0" & text.Substring(1)
caret += 1
End If
End If
If caret = text.Length Then
selectionLength = 1
textInserted = True
text = text & NumberFormatInfo.CurrentInfo.NumberDecimalSeparator & "0"
caret += 1
End If
ElseIf e.Text = NumberFormatInfo.CurrentInfo.NegativeSign Then
textInserted = True
If _this.Text.Contains(NumberFormatInfo.CurrentInfo.NegativeSign) Then
text = text.Replace(NumberFormatInfo.CurrentInfo.NegativeSign, String.Empty)
If caret <> 0 Then
caret -= 1
End If
Else
text = NumberFormatInfo.CurrentInfo.NegativeSign + _this.Text
caret += 1
End If
End If
If Not textInserted Then
text = text.Substring(0, caret) & Convert.ToString(e.Text) & (If((caret < _this.Text.Length), text.Substring(caret), String.Empty))
caret += 1
End If
Try
Dim val As Double = Convert.ToDouble(text)
Dim newVal As Double = ValidateLimits(GetMinimumValue(_this), GetMaximumValue(_this), val)
If val <> newVal Then
text = newVal.ToString()
ElseIf val = 0 Then
If Not text.Contains(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator) Then
text = "0"
End If
End If
Catch
text = "0"
End Try
While text.Length > 1 AndAlso text(0) = "0"c AndAlso String.Empty & text(1) <> NumberFormatInfo.CurrentInfo.NumberDecimalSeparator
text = text.Substring(1)
If caret > 0 Then
caret -= 1
End If
End While
While text.Length > 2 AndAlso String.Empty & text(0) = NumberFormatInfo.CurrentInfo.NegativeSign AndAlso text(1) = "0"c AndAlso String.Empty & text(2) <> NumberFormatInfo.CurrentInfo.NumberDecimalSeparator
text = NumberFormatInfo.CurrentInfo.NegativeSign & text.Substring(2)
If caret > 1 Then
caret -= 1
End If
End While
If caret > text.Length Then
caret = text.Length
End If
_this.Text = text
_this.CaretIndex = caret
_this.SelectionStart = caret
_this.SelectionLength = selectionLength
e.Handled = True
End If
End Sub
Private Shared Function ValidateValue(ByVal mask As MaskType, ByVal value As String, ByVal min As Double, ByVal max As Double) As String
If String.IsNullOrEmpty(value) Then
Return String.Empty
End If
value = value.Trim()
Select Case mask
Case MaskType.[Integer]
Try
Convert.ToInt64(value)
Return value
Catch
End Try
Return String.Empty
Case MaskType.[Decimal]
Try
Convert.ToDouble(value)
Return value
Catch
End Try
Return String.Empty
End Select
Return value
End Function
Private Shared Function ValidateLimits(ByVal min As Double, ByVal max As Double, ByVal value As Double) As Double
If Not min.Equals(Double.NaN) Then
If value < min Then
Return min
End If
End If
If Not max.Equals(Double.NaN) Then
If value > max Then
Return max
End If
End If
Return value
End Function
Private Shared Function IsSymbolValid(ByVal mask As MaskType, ByVal str As String) As Boolean
Select Case mask
Case MaskType.Any
Return True
Case MaskType.[Integer]
If str = NumberFormatInfo.CurrentInfo.NegativeSign Then
Return True
End If
Exit Select
Case MaskType.[Decimal]
If str = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator OrElse str = NumberFormatInfo.CurrentInfo.NegativeSign Then
Return True
End If
Exit Select
End Select
If mask.Equals(MaskType.[Integer]) OrElse mask.Equals(MaskType.[Decimal]) Then
For Each ch As Char In str
If Not [Char].IsDigit(ch) Then
Return False
End If
Next
Return True
End If
Return False
End Function
End Region
End Class
Public Enum MaskType
Any
[Integer]
[Decimal]
End Enum
End Namespace
Figured out the solution. I am testing the length of the text and making sure it is < the max length.
If Not textInserted Then
If _this.Text.Length < _this.MaxLength Then
text = text.Substring(0, caret) & Convert.ToString(e.Text) & (If((caret < _this.Text.Length), text.Substring(caret), String.Empty))
caret += 1
End If
End If
Used this on the integer function.

Resources