WPF NumericUpDown usercontrol in VB.net - wpf

I am creating my own NumericUpDown control in VB. Here is my XAML:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Class="NumericUpDown"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="100">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<TextBox x:Name="txtNum" Grid.Column="0" x:FieldModifier="private" TextChanged="txtNum_TextChanged"/>
<Button x:Name="cmdDown" Grid.Column="1" x:FieldModifier="private" Content="˅" Width="20" Click="cmdDown_Click" />
<Button x:Name="cmdUp" Grid.Column="2" x:FieldModifier="private" Content="˄" Width="20" Click="cmdUp_Click" />
</Grid>
</UserControl>
And here is the VB code behind it:
Class NumericUpDown
Dim _Minimum As Double = 0
Dim _Maximum As Double = 100
Private Sub NumericUpDown()
InitializeComponent()
txtNum.Text = Numeric
End Sub
Public Property Maximum As Double
Get
Return _Maximum
End Get
Set(value As Double)
_Maximum = value
End Set
End Property
Public Property Minimum As Double
Get
Return _Minimum
End Get
Set(value As Double)
_Minimum = value
End Set
End Property
Public Shared ReadOnly NumericProperty As DependencyProperty = DependencyProperty.Register("Numeric", GetType(String), GetType(NumericUpDown), _
New PropertyMetadata(""))
Public Property Numeric As String
Get
Return CType(GetValue(NumericProperty), String)
End Get
Set(value As String)
SetValue(NumericProperty, value)
End Set
End Property
Private Sub cmdUp_Click(sender As Object, e As RoutedEventArgs)
Dim NumValue As Double
NumValue = Val(txtNum.Text)
NumValue += 1
If NumValue > Maximum Then NumValue = Maximum
txtNum.Text = NumValue.ToString
End Sub
Private Sub cmdDown_Click(sender As Object, e As RoutedEventArgs)
Dim NumValue As Double
NumValue = Val(txtNum.Text)
NumValue -= 1
If NumValue < Minimum Then NumValue = Minimum
txtNum.Text = NumValue.ToString
End Sub
Private Sub txtNum_TextChanged(sender As Object, e As TextChangedEventArgs)
Numeric = txtNum.Text
End Sub
End Class
I use it in my page like this:
Put this on page definition:
xmlns:local="clr-namespace:Demo"
And put this in the content section:
<local:NumericUpDown Numeric="{Binding Path=score, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
Of course I have already set DataContext on the container and all other databound controls work as they should. But the textbox in my custom control turned out empty! It doesn't end here. When I type something in the textbox and when I give it some value using decrease and increase button, the value is transferred to my DataTable; which means this usercontrol does work to some extend. Where did I do wrong? Why won't the Textbox content be initialized with the starting value?
After a little more testing. It seems that my usercontrol doesn't work in 'Two-Way'. It doesn't receive data from DataTable, it only propagates value to it. How do I fix it?

The issue is that you are not binding the Text property of txtNum to your Numeric property.
<TextBox x:Name="txtNum" Grid.Column="0" x:FieldModifier="private"
Text="{Binding Path=Numeric, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
Then you can remove txtNum_TextChanged, and modify your up/down click handlers to just update the Numeric property, e.g.:
Private Sub cmdUp_Click(sender As Object, e As RoutedEventArgs)
If Me.Numeric < Me.Maximum Then
Me.Numeric += 1
Else
Me.Numeric = Me.Maximum
End If
End Sub
Private Sub cmdDown_Click(sender As Object, e As RoutedEventArgs)
If Me.Numeric > Me.Minimum Then
Me.Numeric -= 1
Else
Me.Numeric = Me.Minimum
End If
End Sub
Note that there are still lots of issues - a user can enter a value outside of the allowed range, or non-numeric data (which will break things!), etc. For this specific problem, you could check out the Extended EPF Toolkit, which has various up/down controls.

Related

Label with static text and binding

I am trying to get a label to show specific text while also being bound to a variable in the VB.Net code. I can make a binding but I cant get it to add the static text.
What I have so far:
<Label x:Name="TestLabel" Content="{Binding Path=Row, StringFormat='Row #{0}'}"
HorizontalAlignment="Left"
Height="35"
Margin="203,21,0,0"
VerticalAlignment="Top"
Width="83"
FontSize="18">
with
Public Class Row
Implements INotifyPropertyChanged
Private _Row As Byte
Public Property Row() As Byte
Get
Return _Row
End Get
Set(ByVal value As Byte)
_Row = value
OnPropertyChanged(New PropertyChangedEventArgs("Row"))
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
If Not PropertyChangedEvent Is Nothing Then
RaiseEvent PropertyChanged(Me, e)
End If
End Sub
End Class
and
Private Rows As New Row
Public Sub New()
InitializeComponent()
TestLabel.DataContext = Rows
Rows.Row = MyTextBox.Text.HandledStringtoSByte
End Sub
The extension code (since I have a custom extension):
''' <summary>
''' Handles conversion of string variable to Tiny Integer
''' </summary>
''' <param name="s"></param>
''' <param name="I">Returned if conversion fails.</param>
''' <returns>Signed 8bit Integer</returns>
''' <remarks></remarks>
<Runtime.CompilerServices.Extension()> _
Public Function HandledStringtoSByte(ByRef S As String, Optional I As SByte = 0) As SByte
Try
If S = String.Empty Then
Return I
Else
Return SByte.Parse(S)
End If
Catch
Dim result As String = String.Empty
Dim ReturnByte As SByte
Dim Parsed As Byte
For Each Character In S.ToCharArray
If Character = "-" Then
If S.Substring(0, 1).ToString <> "-" Then
Exit For
End If
End If
If Character = "." Then
Exit For
End If
If Byte.TryParse(Character, Parsed) Then
result = result + Parsed.ToString
End If
Next
If result <> String.Empty Then
If SByte.TryParse(result, ReturnByte) Then
Return SByte.Parse(ReturnByte)
Else
If Short.Parse(result) > Short.Parse(SByte.MaxValue.ToString) Then
Return SByte.MaxValue
ElseIf Short.Parse(result) < Short.Parse(SByte.MinValue.ToString) Then
Return SByte.MinValue
Else
Return SByte.Parse(ReturnByte)
End If
End If
Else
Return I
End If
End Try
End Function
Now I thought that using the stringformat in binding would add the static text and place the bound variable into the {0} spot but all is gives me is the bound variable in the label.
What am i doing wrong?
Binding target is Content property which is Object type, that is why you cannot use StringFormat with binding.
Instead use ContentStringFormat property
<Label Content="{Binding Path=Row}"
ContentStringFormat="Row #{0}" />
Another approach: create readonly property in the ViewModel which will represent value in wanted format
Private _Row As Byte
Public Property Row() As Byte
Get
Return _Row
End Get
Set(ByVal value As Byte)
_Row = value
OnPropertyChanged(New PropertyChangedEventArgs("Row"))
OnPropertyChanged(New PropertyChangedEventArgs("RowText"))
End Set
End Property
Public ReadOnly Property RowText As String
Get
Return String.Format("Row #{0}", Me.Row)
End Get
End Property
Then bind this property to the View
<Label Content="{Binding Path=RowText}"/>
The problem is that Binding.StringFormat is "a string that specifies how to format the binding if it displays the bound value as a string". In practice it seems to work only if the target property is of type string - as you pointed out it's working for TextBlock.Text (which is of type string) and not for Label.Content (which is of type object). There are several ways to approach this problem, one of them would be to nest a TextBlock in the Content property:
<Label>
<TextBlock Text="{Binding Path=Row, StringFormat='Row #{0}'}" />
</Label>
This doesn't really introduce any additional complexity to the visual tree since strings are by default presented by TextBlocks.
Otherwise you could create your own converter, or you could go with Fabio's solution and utilize Label.ContentStringFormat property.
Here's a way to bind to multiple properties :
a MultiBinding
an IMultiValueConverter
Code:
Imports System.Globalization
Imports System.Text
Class MainWindow
Public Shared ReadOnly Text1Property As DependencyProperty = DependencyProperty.Register(
"Text1", GetType(String), GetType(MainWindow), New PropertyMetadata(Nothing))
Public Property Text1 As String
Get
Return DirectCast(GetValue(Text1Property), String)
End Get
Set
SetValue(Text1Property, Value)
End Set
End Property
Public Shared ReadOnly Text2Property As DependencyProperty = DependencyProperty.Register(
"Text2", GetType(String), GetType(MainWindow), New PropertyMetadata(Nothing))
Public Property Text2 As String
Get
Return DirectCast(GetValue(Text2Property), String)
End Get
Set
SetValue(Text2Property, Value)
End Set
End Property
Private Sub MainWindow_OnLoaded(sender As Object, e As RoutedEventArgs)
Me.Text1 = "text1"
Me.Text2 = "text2"
End Sub
End Class
Converter:
Class MyConverter
Implements IMultiValueConverter
Public Function Convert(values As Object(), targetType As Type, parameter As Object, culture As CultureInfo) _
As Object Implements IMultiValueConverter.Convert
If values Is Nothing Then
Return DependencyProperty.UnsetValue
End If
Dim sb As New StringBuilder
If values.Length > 0 Then
sb.AppendLine(values(0))
End If
If values.Length > 1 Then
sb.AppendLine(values(1))
End If
Return sb.ToString()
End Function
Public Function ConvertBack(value As Object, targetTypes As Type(), parameter As Object, culture As CultureInfo) _
As Object() Implements IMultiValueConverter.ConvertBack
Throw New NotImplementedException
End Function
End Class
Xaml:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Window"
Title="MainWindow"
Width="525"
Height="350"
Loaded="MainWindow_OnLoaded"
mc:Ignorable="d">
<Grid>
<StackPanel>
<TextBox Text="{Binding ElementName=Window, Path=Text1, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding ElementName=Window, Path=Text2, UpdateSourceTrigger=PropertyChanged}" />
<Label>
<Label.Resources>
<local:MyConverter x:Key="MyConverter" />
</Label.Resources>
<Label.Content>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding ElementName="Window" Path="Text1" />
<Binding ElementName="Window" Path="Text2" />
</MultiBinding>
</Label.Content>
</Label>
</StackPanel>
</Grid>
</Window>

Print input from user in WPF VB

I'm working on project where the user will enter a JobNumber, say (J000001), and when the user hits print the jobnumber will print. With the code below, I'm able to print the numbers, say (001), but I want the user to enter the actual JobNumber (J000001). Any help on this is greatly appreciated.
When I enter the JobNumber (J000001), I get the following error message:
'Invalid CastException was unhandled'
Conversion from string "J000001" to type boolean is not valid.
Below is my VB Code:
Imports System.Globalization
Imports System.Drawing.Printing
Imports System.Drawing
Class MainWindow
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
AddHandler printDocument1.PrintPage, AddressOf printDocument1_PrintPage
End Sub
'Declaration the global variables
Private paperSize As New PaperSize("papersize", 300, 500)
'set the paper size
Private totalnumber As Integer = 0
'this is for total number of items of the list or array
Private itemperpage As Integer = 0
'this is for no of item per page
Private printDocument1 As New PrintDocument()
Private printDialog1 As New System.Windows.Forms.PrintDialog()
Private DefaultFont As New Font("Calibri", 20)
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
If txtStart.Text Then
itemperpage = 1
totalnumber = txtStart.Text
printDialog1.Document = printDocument1
printDocument1.DefaultPageSettings.PaperSize = paperSize
printDialog1.ShowDialog()
'printDocument1.PrinterSettings.PrinterName = "";
printDocument1.Print()
Else
MessageBox.Show("Invalid number")
End If
End Sub
Private Function CheckNumber(str As String)
Dim Num As Double
Return Double.TryParse(str, Num)
End Function
'Define the Printpage event of the printdocument
Private Sub printDocument1_PrintPage(sender As Object, e As System.Drawing.Printing.PrintPageEventArgs)
Dim currentY As Single = 10
While totalnumber <= CInt(txtStart.Text)
' check the number of items
e.Graphics.DrawString(totalnumber.ToString(), DefaultFont, System.Drawing.Brushes.Black, 50, currentY)
'print each item
currentY += 20
' set a gap between every item
totalnumber += 1
'increment count by 1
If itemperpage < 1 Then
' check whether the number of item(per page) is more than 1 or not
itemperpage += 1
' increment itemperpage by 1
' set the HasMorePages property to false , so that no other page will not be added
e.HasMorePages = False
Else
' if the number of item(per page) is more than 1 then add one page
itemperpage = 1
'initiate itemperpage to 0 .
If totalnumber <= Convert.ToInt32(txtStart.Text) Then
e.HasMorePages = True
End If
'e.HasMorePages raised the PrintPage event once per page .
'It will call PrintPage event again
Return
End If
End While
End Sub
End Class
XAML code
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="175" Width="303">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="1.5*" />
</Grid.ColumnDefinitions>
<Label Content="Start Number:" />
<TextBox x:Name="txtStart" Grid.Column="1" />
<Button Grid.Row="2" Grid.ColumnSpan="2" Content="Print" Click="Button_Click" />
</Grid>
</Window>
The issue is your If statement in the Button_Click method.
If txtStart.Text Then
The If takes a boolean, but you are passing it a string. VB.Net is trying to convert the string to a boolean. 001 works, because it can convert that. J000001 is not able to be converted to a boolean.
Do you mean to check if there is a value entered?
If !String.IsNullOrWhiteSpace(txtStart.Text) Then
You will also run into a similar issue when assigning the value to totalnumber.
You are comparing the value of the text box as a boolean, and then assigning the value of your Text Box to a variable of type Integer.
If txtStart.Text Then
should probably be something like:
If !String.IsNullOrEmpty(txtStart.Text) Then
And then if you job numbers are alpha numeric, use a variable of type String instead of Integer.

How to bind object to UserControl?

I have a CustomerOrder class which has 2 properties namely CustomerName and NumberOfOrders. I have a Usercontrol to show these two properties, namely ucCustomerOrder.
Following the code corresponding
Customer Class
Public Class CustomerOrder
Private m_CustomerName As String = String.Empty
Private m_NumberOfOrders As Decimal = 0
Public Property CustomerName As String
Get
Return m_CustomerName
End Get
Set(ByVal value As String)
m_CustomerName = value
End Set
End Property
Public Property NumberOfOrders As Decimal
Get
Return m_NumberOfOrders
End Get
Set(ByVal value As Decimal)
m_NumberOfOrders = value
End Set
End Property
End Class
UserControl XAML
<UserControl x:Class="ucCustomerOrder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="99" d:DesignWidth="300">
<Grid>
<Label Name="lblCustomerName" Content="{Binding Path=CustomerName}" Height="28" HorizontalAlignment="Left" Margin="23,12,0,0" VerticalAlignment="Top" Width="237" />
<Label Name="lblNoOfOrders" Content="{Binding Path=NumberOfOrders}" Height="28" HorizontalAlignment="Left" Margin="23,46,0,0" VerticalAlignment="Top" Width="237" />
</Grid>
</UserControl>
Code behind the User control is as follows.
Public Class ucCustomerOrder
Private m_CustomerOrder As CustomerOrder
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
m_CustomerOrder = New CustomerOrder
Me.DataContext = m_CustomerOrder
End Sub
Public Property CustomerOrder As CustomerOrder
Get
Return m_CustomerOrder
End Get
Set(ByVal value As CustomerOrder)
m_CustomerOrder = value
Me.DataContext = m_CustomerOrder
End Set
End Property
End Class
In my main window I have a combo box control and ucCustomerOrder (User Control) and also I have list of Customer’s orders in Dictionary object. Like as follows
XAML For main windows looks like
<Window x:Class="DictionaryTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DictionaryTest" Height="182" Width="300" xmlns:my="clr-namespace:TestUserControls">
<Grid>
<ComboBox Height="23" Width="254" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="12,12,0,0" Name="cboCustomerOrders" ItemsSource="{Binding}" DisplayMemberPath="Key" SelectedValuePath="Value"/>
<my:ucCustomerOrder HorizontalAlignment="Left" VerticalAlignment="Top" Margin="12,56,0,0" x:Name="UcCustomerOrder1"/>
</Grid>
</Window>
Code Behind for my main window as follows.
Public Class DictionaryTest
Private Function GetCustomers() As Dictionary(Of String, CustomerOrder)
Dim customerList As New Dictionary(Of String, CustomerOrder)
Dim customer1 As New CustomerOrder
With customer1
.CustomerName = "XXXX"
.NumberOfOrders = 100
End With
Dim customer2 As New CustomerOrder
With customer2
.CustomerName = "ZZZZ"
.NumberOfOrders = 150
End With
customerList.Add("X-Customer", customer1)
customerList.Add("Z-Customer", customer2)
Return customerList
End Function
Private Sub DictionaryTest_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
Try
cboCustomerOrders.ItemsSource = GetCustomers()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
End Class
How can I set my selected CustomerOrder object to the user control using Binding?

WPF Parent Element Lost Focus

I am developing a WPF application. In one of my pages I have a DockPanel with 3 ComboBoxes within it. These 3 ComboBoxes are dependent on each other. So, I have a binding group on the DockPanel and a validation rule for that binding group.
I would like to validate these 3 ComboBoxes when the DockPanel loses focus; however, the DockPanel's LostFocus event is fired when the user clicks on one of the child ComboBoxes within it!
I would have thought that if the child control has focus, the parent control would also have focus...however this does not seem to be the case.
I am looking for a solution to perform validation on the binding group when the DockPanel loses focus...where the focus is lost to a control that is not one of its children.
Edit:
So, I created as simple an application as I could to demonstrate the problem.
I have a "FertilizerCombination" class that contains 3 integers that make up the Fertilizer. These must be unique within the application. Currently the only combination that is not available is 10-10-10. This is a hard coded value in the class.
Public Class FertilizerCombination
Private _nitrogen As Integer
Private _phosphorous As Integer
Private _potassium As Integer
<System.ComponentModel.DataAnnotations.Range(1, 20)> _
Public Property Nitrogen As Integer
Get
Return _nitrogen
End Get
Set(value As Integer)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, NitrogenValidationContext)
_nitrogen = value
End Set
End Property
<System.ComponentModel.DataAnnotations.Range(1, 20)> _
Public Property Phosphorous As Integer
Get
Return _phosphorous
End Get
Set(value As Integer)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, PhosphorousValidationContext)
_phosphorous = value
End Set
End Property
<System.ComponentModel.DataAnnotations.Range(1, 20)> _
Public Property Potassium As Integer
Get
Return _potassium
End Get
Set(value As Integer)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, PotassiumValidationContext)
_potassium = value
End Set
End Property
Public Sub New()
End Sub
Public Sub New(ByVal nitrogen As Integer, ByVal phosphorous As Integer, ByVal potassium As Integer)
Me.Nitrogen = nitrogen
Me.Phosphorous = phosphorous
Me.Potassium = potassium
End Sub
Public Shared Function FertilizerCombinationAvailable(ByVal nitrogen As Integer, ByVal phosphorous As Integer, ByVal potassium As Integer) As Boolean
'Checking against combinations already used'
If nitrogen = "10" And phosphorous = "10" And potassium = "10" Then
'Combination has already been used'
Return False
End If
'Combination was not used yet'
Return True
End Function
Public Sub SetFertilizerCombination(ByVal nitrogen As Integer, ByVal phosphorous As Integer, ByVal potassium As Integer)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(nitrogen, NitrogenValidationContext)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(phosphorous, PhosphorousValidationContext)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(potassium, PotassiumValidationContext)
If FertilizerCombination.FertilizerCombinationAvailable(nitrogen, phosphorous, potassium) = False Then
Throw New ArgumentException("This fertilizer combination has already been used")
End If
Me.Nitrogen = nitrogen
Me.Phosphorous = phosphorous
Me.Potassium = potassium
End Sub
Private NitrogenValidationContext = New System.ComponentModel.DataAnnotations.ValidationContext(Me, Nothing, Nothing) With {.MemberName = "Nitrogen"}
Private PhosphorousValidationContext = New System.ComponentModel.DataAnnotations.ValidationContext(Me, Nothing, Nothing) With {.MemberName = "Phosphorous"}
Private PotassiumValidationContext = New System.ComponentModel.DataAnnotations.ValidationContext(Me, Nothing, Nothing) With {.MemberName = "Potassium"}
End Class
I have a created a Validation Rule for the Fertilizer Combination Class:
Public Class FertilizerCombinationValidationRule
Inherits ValidationRule
Public Overrides Function Validate(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As System.Windows.Controls.ValidationResult
Dim bg As BindingGroup = TryCast(value, BindingGroup)
If bg IsNot Nothing AndAlso bg.Items.Count > 0 Then
'The BindingGroup Items property contains the original object(s)'
Dim fertilizerCombo As FertilizerCombination = TryCast(bg.Items(0), FertilizerCombination)
'Using the BindingGroups GetValue method to retrieve the user provided values'
Dim proposedNitrogen As Integer
Dim proposedPhosphorous As Integer
Dim proposedPotassium As Integer
Try
proposedNitrogen = bg.GetValue(fertilizerCombo, "Nitrogen")
proposedPhosphorous = bg.GetValue(fertilizerCombo, "Phosphorous")
proposedPotassium = bg.GetValue(fertilizerCombo, "Potassium")
If FertilizerCombination.FertilizerCombinationAvailable(proposedNitrogen, proposedPhosphorous, proposedPotassium) = False Then
Return New ValidationResult(False, "This fertializer combination has already been used")
End If
Catch noValue As System.Windows.Data.ValueUnavailableException
'a binding was not properly bound yet'
End Try
End If
Return New ValidationResult(True, Nothing)
End Function
End Class
I have a "PlantSolution" class that has a FertilizerCombination property. I also have a VM for that class so that it's easy for binding in the XAML:
Public Class PlantSolutionVM
Public Property PlantSolution As PlantSolution
Public Sub New()
PlantSolution = New PlantSolution("Produce Blooms", "Using this fertilizer will help your plant produce flowers!", 10, 10, 20)
End Sub
End Class
Public Class PlantSolution
Private _name As String
Private _description As String
Private _fertilizer As FertilizerCombination
<System.ComponentModel.DataAnnotations.Required()>
Public Property Name As String
Get
Return _name
End Get
Set(value As String)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, NameValidationContext)
_name = value
End Set
End Property
<System.ComponentModel.DataAnnotations.Required()>
Public Property Description As String
Get
Return _description
End Get
Set(value As String)
System.ComponentModel.DataAnnotations.Validator.ValidateProperty(value, DescriptionValidationContext)
_description = value
End Set
End Property
Public ReadOnly Property Fertilizer As FertilizerCombination
Get
Return _fertilizer
End Get
End Property
Public Sub New(name As String, description As String, nitrogen As Integer, phosphorous As Integer, potassium As Integer)
_fertilizer = New FertilizerCombination(nitrogen, phosphorous, potassium)
Me.Name = name
Me.Description = description
End Sub
Private NameValidationContext = New System.ComponentModel.DataAnnotations.ValidationContext(Me, Nothing, Nothing) With {.MemberName = "Name"}
Private DescriptionValidationContext = New System.ComponentModel.DataAnnotations.ValidationContext(Me, Nothing, Nothing) With {.MemberName = "Description"}
End Class
Now here is my XAML for a window called "FunWithFocus":
<Grid>
<Grid.Resources>
<x:Array x:Key="CombinationOptions" Type="sys:Int32" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:Int32>0</sys:Int32>
<sys:Int32>1</sys:Int32>
<sys:Int32>2</sys:Int32>
<sys:Int32>3</sys:Int32>
<sys:Int32>4</sys:Int32>
<sys:Int32>5</sys:Int32>
<sys:Int32>6</sys:Int32>
<sys:Int32>7</sys:Int32>
<sys:Int32>8</sys:Int32>
<sys:Int32>9</sys:Int32>
<sys:Int32>10</sys:Int32>
<sys:Int32>11</sys:Int32>
<sys:Int32>12</sys:Int32>
<sys:Int32>13</sys:Int32>
<sys:Int32>14</sys:Int32>
<sys:Int32>15</sys:Int32>
<sys:Int32>16</sys:Int32>
<sys:Int32>17</sys:Int32>
<sys:Int32>18</sys:Int32>
<sys:Int32>19</sys:Int32>
<sys:Int32>20</sys:Int32>
</x:Array>
<local:PlantSolutionVM x:Key="PlantSolutionVM" />
<Style TargetType="DockPanel">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Grid DataContext="{Binding Source={StaticResource PlantSolutionVM}, Path=PlantSolution}" Margin="50">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Solution Name:" Grid.Row="0" Grid.Column="0" Margin="5"/>
<TextBox x:Name="ItemName" Text="{Binding Name, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Grid.Row="0" Grid.Column="1" VerticalAlignment="Top" Margin="5"/>
<TextBlock Text="Description of Problem:" Grid.Row="1" Grid.Column="0" Margin="5"/>
<TextBox x:Name="ItemDescription" Text="{Binding Description, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Grid.Row="1" Grid.Column="1" Margin="5"/>
<TextBlock Text="Recommended Fertilizer:" Grid.Row="2" Grid.Column="0" Margin="5"/>
<DockPanel x:Name="FertilizerCombinationContainer" DataContext="{Binding Fertilizer}" Grid.Row="2" Grid.Column="1" Margin="5" HorizontalAlignment="Left" VerticalAlignment="Top">
<DockPanel.BindingGroup>
<BindingGroup NotifyOnValidationError="True">
<BindingGroup.ValidationRules>
<local:FertilizerCombinationValidationRule />
</BindingGroup.ValidationRules>
</BindingGroup>
</DockPanel.BindingGroup>
<ComboBox x:Name="NitrogenValue" HorizontalAlignment="Left" VerticalAlignment="Top"
ItemsSource="{StaticResource CombinationOptions}"
SelectedItem="{Binding Nitrogen}"/>
<ComboBox x:Name="PhosphorousValue" HorizontalAlignment="Left" VerticalAlignment="Top"
ItemsSource="{StaticResource CombinationOptions}"
SelectedItem="{Binding Phosphorous}"/>
<ComboBox x:Name="PotatssiumValue" HorizontalAlignment="Left" VerticalAlignment="Top"
ItemsSource="{StaticResource CombinationOptions}"
SelectedItem="{Binding Potassium}"/>
</DockPanel>
<Button x:Name="SaveIt" Content="Save" Grid.Row="3" Grid.Column="1"/>
</Grid>
</Grid>
And here is the Code Behind for the page:
Class FunWithFocus
Private Sub FertilizerCombinationContainer_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles FertilizerCombinationContainer.Loaded
'FertilizerCombinationContainer.BindingGroup.CancelEdit()'
'FertilizerCombinationContainer.BindingGroup.BeginEdit()'
End Sub
Private Sub FertilizerCombinationContainer_LostFocus(sender As Object, e As System.Windows.RoutedEventArgs) Handles FertilizerCombinationContainer.LostFocus
'This will get fired if the user drops down one of the comboboxes'
End Sub
'This is how I am currently handling the lost focus...but it doesn't fire if the user clicks somewhere that doesn't take keyboard focus'
'Private Sub FertilizerCombinationContainer_IsKeyboardFocusWithinChanged(sender As Object, e As System.Windows.DependencyPropertyChangedEventArgs) Handles FertilizerCombinationContainer.IsKeyboardFocusWithinChanged'
' If FertilizerCombinationContainer.IsKeyboardFocusWithin = False Then'
' FertilizerCombinationContainer.BindingGroup.ValidateWithoutUpdate()'
' If Validation.GetErrors(FertilizerCombinationContainer).Count = 0 Then'
' FertilizerCombinationContainer.BindingGroup.CommitEdit()'
' Dim bg As BindingGroup = FertilizerCombinationContainer.BindingGroup'
' Dim fertilizerCombo As FertilizerCombination = bg.Items(0)'
' Dim proposedNitrogen As Integer'
' Dim proposedPhosphorous As Integer'
' Dim proposedPotassium As Integer'
' Try'
' proposedNitrogen = bg.GetValue(fertilizerCombo, "Nitrogen")'
' proposedPhosphorous = bg.GetValue(fertilizerCombo, "Phosphorous")'
' proposedPotassium = bg.GetValue(fertilizerCombo, "Potassium")'
' ''there was a change: set it'
' fertilizerCombo.SetFertilizerCombination(proposedNitrogen, proposedPhosphorous, proposedPotassium)'
' Catch noValue As System.Windows.Data.ValueUnavailableException'
' ''a binding was not properly bound yet'
' End Try'
' End If'
' End If'
'End Sub'
End Class
If you put a break point on the method that handles FertilizerCombinationContainer's LostFocus event, you will notice that it is fired when you select one of the ComboBoxes even though it is a child of the FertilizerContainer element.
Thank you,
-Frinny

Binding multiple progress bars

Referring to screenshot below, I have the following view and viewmodel setup to represent progress bar download. When I add more than 1 downloads to this collection of downloads, it seems that download 1 will overwrite download 2's Progress() public property.
Example below is download 1 is downloading 1MB it will finish first and compared with download 2 downloading 5MB.
When download 1 finish, download 2 (hello2 example below) progress bar just stop there but the bytes are still being downloaded at background until 5MB is reached.
How can I change the code to ensure download 1 does not interfere with download 2's progress bar UI. I've tried modifying Progress() to private but then both progress bar UI is not shown
View
<ItemsControl Name="MyItemsControl" ItemsSource="{Binding GameDownloads}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid DockPanel.Dock="Right">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding GameName}" />
<ProgressBar Grid.Row="0" Grid.Column="1" Minimum="0" Maximum="100" Value="{Binding Progress, Mode=OneWay}" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding StatusText}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
DownloadAppViewModel
Public Class DownloadAppViewModel
Inherits ViewModelBase
Private ReadOnly WC As WebClient
Public Sub New(ByVal Name As String, ByVal URL As String, ByVal FileName As String)
_gameName = Name
WC = New WebClient
AddHandler WC.DownloadFileCompleted, AddressOf DownloadCompleted
AddHandler WC.DownloadProgressChanged, AddressOf DownloadProgressChanged
WC.DownloadFileAsync(New Uri(URL), FileName)
End Sub
Private Sub DownloadCompleted(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
End Sub
Private Sub DownloadProgressChanged(ByVal sender As [Object], ByVal e As DownloadProgressChangedEventArgs)
If _totalSize = 0 Then
_totalSize = e.TotalBytesToReceive
End If
DownloadedSize = e.BytesReceived
End Sub
Private _gameName As String
Public Property GameName() As String
Get
Return _gameName
End Get
Set(ByVal value As String)
_gameName = value
Me.OnPropertyChanged("GameName")
End Set
End Property
Public ReadOnly Property StatusText() As String
Get
If _downloadedSize < _totalSize Then
Return String.Format("Downloading {0} MB of {1} MB", _downloadedSize, _totalSize)
End If
Return "Download completed"
End Get
End Property
Private _totalSize As Long
Private Property TotalSize() As Long
Get
Return _totalSize
End Get
Set(ByVal value As Long)
_totalSize = value
OnPropertyChanged("TotalSize")
OnPropertyChanged("Progress")
OnPropertyChanged("StatusText")
End Set
End Property
Private _downloadedSize As Long
Private Property DownloadedSize() As Long
Get
Return _downloadedSize
End Get
Set(ByVal value As Long)
_downloadedSize = value
OnPropertyChanged("DownloadedSize")
OnPropertyChanged("Progress")
OnPropertyChanged("StatusText")
End Set
End Property
Public ReadOnly Property Progress() As Double
Get
If _totalSize <> 0 Then
Return 100.0 * _downloadedSize / _totalSize
Else
Return 0.0
End If
End Get
End Property
End Class

Resources