Enable ValidationRule if checkbox is checked - wpf

I want to validate some controls, but only if my checkbox is checked. (in this example i only show one textbox to validate)
I tried to work with BindingGroup, but this means that the checkbox is validated too which gives me wrong results.
Now I try it with MultiBinding:
<CheckBox Name="chkUseLqv"
Grid.Row="6"
Grid.Column="0"
Margin="0,0,8,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="Use LQV"
IsChecked="{Binding LqvConfigurationEnabled}" />
[...]
<GroupBox Grid.Row="1"
Grid.RowSpan="6"
Grid.Column="4"
Margin="5"
Header="LQV Configuration"
IsEnabled="{Binding LqvConfigurationEnabled}">
<Grid>
<TextBox Name="txtLqvDatabaseServer"
Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Center"
Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}">
<TextBox.Text>
<MultiBinding Converter="{Converters:LqvConfigurationMultiBindingConverter}" UpdateSourceTrigger="PropertyChanged">
<MultiBinding.ValidationRules>
<LocalValidationRules:LqvDatabaseServerValidator />
</MultiBinding.ValidationRules>
<Binding ElementName="chkUseLqv" Path="IsChecked" />
<Binding Path="LqvDatabaseServer" />
</MultiBinding>
</TextBox.Text>
</TextBox>
</Grid>
</GroupBox>
My Validator:
Imports System.Text.RegularExpressions
Namespace ValidationRules
Public Class LqvDatabaseServerValidator
Inherits ValidationRule
Public Overrides Function Validate(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As System.Windows.Controls.ValidationResult
Dim useLqv = CType(value, Object()).OfType(Of Boolean).First()
Dim valueFromSource = CType(value, Object()).OfType(Of String).FirstOrDefault()
If useLqv Then
If String.IsNullOrEmpty(valueFromSource) Then
Return New ValidationResult(False, "This field is required!")
End If
If Not (
Regex.IsMatch(valueFromSource, "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") OrElse
Regex.IsMatch(valueFromSource, "^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$")
) Then
Return New ValidationResult(False, "Invalid input!")
End If
End If
Return ValidationResult.ValidResult
End Function
End Class
End Namespace
The converter:
Imports System.Windows.Markup
Namespace Converters
Public Class LqvConfigurationMultiBindingConverter
Inherits MarkupExtension
Implements IMultiValueConverter
Public Sub New()
End Sub
Private _orig As Object()
Public Function Convert(ByVal values() As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IMultiValueConverter.Convert
_orig = values
Return values.OfType(Of String).FirstOrDefault()
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetTypes() As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object() Implements System.Windows.Data.IMultiValueConverter.ConvertBack
Return _orig
End Function
Public Overrides Function ProvideValue(ByVal serviceProvider As System.IServiceProvider) As Object
Return New LqvConfigurationMultiBindingConverter()
End Function
End Class
End Namespace
But it doesn't work. Can you help me?

I did it this way:
Define a style in resources:
<Style x:Key="lqvDependingFields" TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate />
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=chkUseLqv, Path=IsChecked}" Value="true">
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ValidationErrorTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
Have a checkbox:
<CheckBox Name="chkUseLqv"
Grid.Row="6"
Grid.Column="0"
Margin="0,0,8,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="Use LQV"
IsChecked="{Binding LqvConfigurationEnabled}" />
Use the style on your control:
<TextBox Name="txtLqvDatabaseServer"
Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Center"
DependencyProperties:AttachedWindowProperties.HasValidationError="{Binding LqvDatabaseServerValidationError}"
Style="{StaticResource lqvDependingFields}">
<TextBox.Text>
<Binding NotifyOnSourceUpdated="True"
Path="LqvDatabaseServer"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<LocalValidationRules:LqvDatabaseServerValidator ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Dependency Property:
#Region "HasValidationErrorProperty"
Public Shared ReadOnly HasValidationErrorProperty As DependencyProperty =
DependencyProperty.RegisterAttached("HasValidationError", GetType(Boolean), GetType(AttachedWindowProperties),
New FrameworkPropertyMetadata(False, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
Nothing, AddressOf CoerceHasValidationError))
Public Shared Function GetHasValidationError(ByVal d As DependencyObject) As Boolean
Return CBool(d.GetValue(HasValidationErrorProperty))
End Function
Public Shared Sub SetHasValidationError(ByVal d As DependencyObject, ByVal value As Boolean)
d.SetValue(HasValidationErrorProperty, value)
End Sub
Private Shared Function CoerceHasValidationError(ByVal d As DependencyObject, ByVal baseValue As Object) As Object
Dim result As Boolean = CBool(baseValue)
If BindingOperations.IsDataBound(d, HasValidationErrorProperty) Then
If GetHasErrorDescriptor(d) Is Nothing Then
Dim desc = DependencyPropertyDescriptor.FromProperty(Validation.HasErrorProperty, d.GetType)
desc.AddValueChanged(d, AddressOf OnHasErrorChanged)
SetHasErrorDescriptor(d, desc)
result = Validation.GetHasError(d)
End If
Else
If GetHasErrorDescriptor(d) IsNot Nothing Then
Dim desc As DependencyPropertyDescriptor = GetHasErrorDescriptor(d)
desc.RemoveValueChanged(d, AddressOf OnHasErrorChanged)
SetHasErrorDescriptor(d, Nothing)
End If
End If
Return result
End Function
Private Shared Sub OnHasErrorChanged(ByVal sender As Object, ByVal e As EventArgs)
Dim d As DependencyObject = TryCast(sender, DependencyObject)
If d IsNot Nothing Then
d.SetValue(HasValidationErrorProperty, d.GetValue(Validation.HasErrorProperty))
End If
End Sub
Private Shared ReadOnly HasErrorDescriptorProperty As DependencyProperty =
DependencyProperty.RegisterAttached("HasErrorDescriptor", GetType(DependencyPropertyDescriptor), GetType(AttachedWindowProperties))
Private Shared Function GetHasErrorDescriptor(ByVal d As DependencyObject) As DependencyPropertyDescriptor
Return CType(d.GetValue(HasErrorDescriptorProperty), DependencyPropertyDescriptor)
End Function
Private Shared Sub SetHasErrorDescriptor(ByVal d As DependencyObject, ByVal value As DependencyPropertyDescriptor)
d.SetValue(HasErrorDescriptorProperty, value)
End Sub
#End Region
Where LqvDatabaseServer and LqvDatabaseServerValidationErrors are properties in my ViewModel.

Related

TextBox in WPF TreeView not updating source

I cannot tell if I fundamentally don't understand something here, or have just done something silly.
I have a tree view with two types of templates.
Nodes that are HierarchicalDataTemplate with a TextBlock
Leaves that are DataTemplate with a TextBlock and a TextBox3.
The binding from source is working fine in all cases and reading the Name or Name & Value properties from the underlying classes, and updating if from any changes from the code behind.
Both the TreeNode and TreeLeaf class implement INotifyPropertyChanged
However the binding of the TextBox.text property back to the TreeLeaf.Value property (with its getter) does not seem to work.
XAML
<TreeView Name="ItemsTree" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding}">
<TreeView.Resources>
<Color x:Key="detailMark">#FFA1A9B3</Color>
<SolidColorBrush x:Key="detailMarkBrush" Color="{StaticResource ResourceKey=detailMark}" />
<Style x:Key="flatTextBox" TargetType="{x:Type TextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Rectangle Stroke="{StaticResource ResourceKey=detailMarkBrush}" StrokeThickness="0"/>
<TextBox Margin="1" Text="{TemplateBinding Text}" BorderThickness="0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<HierarchicalDataTemplate DataType="{x:Type local:TreeNode}" ItemsSource="{Binding Children, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}">
<TextBlock Text="{Binding Name, NotifyOnSourceUpdated=True}">
</TextBlock>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:TreeLeaf}" >
<StackPanel Orientation="Horizontal">
<TextBlock Width="150" Text="{Binding Name, NotifyOnSourceUpdated=True}"/>
<TextBox Width="150" Text="{Binding Value, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Style="{StaticResource ResourceKey=flatTextBox}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
And then code behind for adding the items to the tree
ItemsTree.Items.Add(treeRoot)
The TreeLeaf class
Public Class TreeLeaf
Implements INotifyPropertyChanged, IEditableObject
Private m_Key As String
Private m_Value As String
Private m_Parent As TreeNode
Private temp_Item As TreeLeaf = Nothing
Private m_Editing As Boolean = False
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub New()
m_Key = ""
m_Value = ""
End Sub
Public Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Public Sub BeginEdit() Implements IEditableObject.BeginEdit
If Not m_Editing Then
temp_Item = MemberwiseClone()
m_Editing = True
End If
End Sub
Public Sub EndEdit() Implements IEditableObject.EndEdit
If m_Editing = True Then
temp_Item = Nothing
m_Editing = False
End If
End Sub
Public Sub CancelEdit() Implements IEditableObject.CancelEdit
If m_Editing = True Then
m_Key = temp_Item.m_Key
m_Value = temp_Item.m_Value
m_Editing = False
End If
End Sub
Public Property Parent As TreeNode
Get
Return m_Parent
End Get
Set(value As TreeNode)
m_Parent = value
NotifyPropertyChanged("Parent")
End Set
End Property
Public ReadOnly Property Name As String
Get
Return m_Key & " : "
End Get
End Property
Public Property Key As String
Get
Return m_Key
End Get
Set(ByVal value As String)
If Not value = m_Key Then
m_Key = value
NotifyPropertyChanged("Key")
End If
End Set
End Property
Public Property Value As String
Get
Return m_Value
End Get
Set(ByVal value As String)
If Not value = m_Value Then
m_Value = value
NotifyPropertyChanged("Value")
End If
End Set
End Property
End Class
I have seen mentions of setting the DataContext but I don't understand how that would apply to this situation.
Well, unfortunately figured it out.
The style template for the text box was overriding the binding.
The line with TemplateBinding seems to default to a one way from source
<TextBox Margin="1" Text="{TemplateBinding Text}" BorderThickness="0"/>
and it now works if you change it too
<TextBox Margin="1" Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" BorderThickness="0"/>
It's not possible to tell from your code whether the binding works or not since the ViewModel code is complicated enough and also depends on the treeview viewmodel code etc...
One way to debug these types of problems is to create a Converter and make it part of the binding that should be debugged. Setting a breakpoint within the Converter will show whether the binding is called as expected when the value in the source or destination has changed. The code for the converter is realy simple:
Imports System
Imports System.Globalization
Imports System.Windows.Data
Namespace MyApp.Converters
Public Class DebugConverter
Inherits IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object
Return value
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object
Return value
End Function
End Class
End Namespace
Its integration into a questionable binding is also simple. Add it via its namespace (eg MyApp.Converters) into the XAML:
xmlns:conv="clr-namespace:MyApp.Converters"
...and create an instance via this statement in the Resource section of your TreeView code listing:
<conv:DebugConverter x:Key="DebugConverter"/>
...and last but not least integrate the converter into a binding you want to debug:
<TextBox
Width="150"
Style="{StaticResource ResourceKey=flatTextBox}"
Text="{Binding Value, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Converter={StaticResource DebugConverter}}" />
Now, when you set a breakpoint in the Convert and ConvertBack method you should see these methods being invoked when you start the application. If you do not see it in either direction you know the binding is either not working or the value is not being updated and you can then search further for the cause.
Hope this helps.

IValueConverter is only called first time databound TreeView is displayed

I have a TreeView that is bound to an ObservableCollection (that properly implements IPropertyNotifyChanged). Each TreeViewItem is a HierarchicalDataTemplate. I have a Converter on the TextBlock that is bound to 'amount' - that changes the foreground colour to red if the string represents a negative number. The first time the TreeView is loaded the amounts all display correctly. However if the underlying ObservableCollection is changed then the amount changes correctly but the colour doesn't (ie a negative 'amount' is shown in white not red).
I have tried using both a IValueConverter and IMultiValueConverter. I have ensured everything is bound with UpdateSourceTrigger=PropertyChanged. The converter just isn't getting called.
What do I need to do to get the converter to be called every time the 'amount' changes??
Thanks
Andy
Template:
<!-- Data templates-->
<HierarchicalDataTemplate x:Key="RealTemplate" DataType="{x:Type l:Account}" ItemsSource="{Binding Path=children}">
<DockPanel LastChildFill="True">
<TextBlock x:Name="AccountTitle" Text="{Binding Path=title}" Foreground="White" DockPanel.Dock="Left"/>
<TextBox x:Name="EditAccountTitle" Text="{Binding Path=title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource RoundedTextBox}" FontWeight="Bold" LostFocus="tvLostFocus" PreviewKeyDown="tvKeyDown" LostKeyboardFocus="tvLostFocus" Visibility="Collapsed" DockPanel.Dock="Left" l:FocusExtension.IsFocused="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}}" CaretIndex="{x:Static sys:Int32.MaxValue}"/>
<TextBlock Text="{Binding Path=amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DockPanel.Dock="Right" TextAlignment="Right">
<TextBlock.Foreground>
<MultiBinding Converter="{StaticResource GetColourConverterAmountM}" UpdateSourceTrigger="PropertyChanged">
<Binding/>
</MultiBinding>
</TextBlock.Foreground>
</TextBlock>
</DockPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=isEditable}" Value="True">
<Setter TargetName="AccountTitle" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="EditAccountTitle" Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=isEditable}" Value="False">
<Setter TargetName="AccountTitle" Property="Visibility" Value="Visible"/>
<Setter TargetName="EditAccountTitle" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource TreeViewTopItemConverter}}" Value="False">
<DataTrigger.Setters>
<Setter Property="ContextMenu" Value="{StaticResource RealAccountMenu}"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource TreeViewTopItemConverter}}" Value="True">
<DataTrigger.Setters>
<Setter Property="ContextMenu" Value="{StaticResource CategoryMenu}"/>
</DataTrigger.Setters>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
Converter:
Public Class GetColourConverterAmountM
Implements IMultiValueConverter
Function Convert(ByVal values() As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As Globalization.CultureInfo) As Object Implements IMultiValueConverter.Convert
If values Is Nothing Then
Return New SolidColorBrush(Colors.White)
ElseIf (Val(values(0).amount) >= 0) Then
Return New SolidColorBrush(Colors.White)
Else
Return New SolidColorBrush(Colors.Red)
End If
End Function
Function ConvertBack(ByVal value As Object, ByVal targetTypes() As Type, ByVal parameter As Object, ByVal culture As Globalization.CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
Return Nothing
End Function
End Class
ObservableCollection:
Public Class Account
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged()
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Nothing))
End Sub
Private _name, _title, _amount, _target As String
Private _ID, _type, _category, _column As Integer
Private _isEditable, _isNodeExpanded, _isNodeSelected As Boolean
Private _children As ObservableCollection(Of Account)
Public Sub New(__ID As Integer, __name As String, __title As String, __amount As String, __target As String, __type As Integer, __category As Integer, __column As Integer)
children = New ObservableCollection(Of Account)
_ID = __ID
_name = __name
_title = __title
_amount = __amount
_target = __target
_category = __category
_type = __type
_column = __column
_isEditable = False
_isNodeExpanded = True
_isNodeSelected = False
End Sub
Property ID As Integer
Get
Return _ID
End Get
Set(value As Integer)
_ID = value
NotifyPropertyChanged()
End Set
End Property
Property name As String
Get
Return _name
End Get
Set(value As String)
_name = value
NotifyPropertyChanged()
End Set
End Property
Property title As String
Get
Return _title
End Get
Set(value As String)
_title = value
NotifyPropertyChanged()
End Set
End Property
Property amount As String
Get
Return _amount
End Get
Set(value As String)
_amount = value
NotifyPropertyChanged()
End Set
End Property
Property target As String
Get
Return _target
End Get
Set(value As String)
_target = value
NotifyPropertyChanged()
End Set
End Property
Property category As Integer
Get
Return _category
End Get
Set(value As Integer)
_category = value
NotifyPropertyChanged()
End Set
End Property
Property type As Integer
Get
Return _type
End Get
Set(value As Integer)
_type = value
NotifyPropertyChanged()
End Set
End Property
Property column As Integer
Get
Return _column
End Get
Set(value As Integer)
_column = value
NotifyPropertyChanged()
End Set
End Property
Property isEditable As Boolean
Get
Return _isEditable
End Get
Set(value As Boolean)
_isEditable = value
NotifyPropertyChanged()
End Set
End Property
Property isNodeExpanded As Boolean
Get
Return _isNodeExpanded
End Get
Set(value As Boolean)
_isNodeExpanded = value
NotifyPropertyChanged()
End Set
End Property
Property isNodeSelected As Boolean
Get
Return _isNodeSelected
End Get
Set(value As Boolean)
_isNodeSelected = value
NotifyPropertyChanged()
End Set
End Property
Property children As ObservableCollection(Of Account)
Get
Return _children
End Get
Set(value As ObservableCollection(Of Account))
_children = value
NotifyPropertyChanged()
End Set
End Property
End Class
You need to bind TextBlock's Foreground property to amount, and use converter to convert amount to color. With that Foreground will be updated whenever amount value changed. For example (not using multibinding) :
<TextBlock Text="{Binding Path=amount, Mode=TwoWay}"
Foreground="{Binding Path=amount,
Converter="{StaticResource GetColourConverterAmount}"
DockPanel.Dock="Right" TextAlignment="Right">

WPF VB.NET Binding Image Source From String in Data Template

My first WPF application (please be gentle!), and I'm making an app based on events during a football match. So I've set up a Player class, and one of the properties is Nationality eg: Scotland. Then I have a DataTemplate, but rather than the string "Scotland" showing up, I would like an image of the national flag to show up instead. All my images are in Images/Countries then are named Scotland.png etc...
The line of code in my data template which I need the binding is:
<Image x:Name="Player_Nationality_Image" Margin="0,0,0,0" Grid.Column="4"Source="Images/Countries/Scotland.png" Height="14" Width="14"/>
Is there an easy way to change my class slightly and bind the image source based on this Nationality property alone? Do I need something in between to convert this?
Here is my Player.vb class:
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Public Class Player
Implements INotifyPropertyChanged
Private playerFirstNameValue As String
Private playerSurnameValue As String
Private playerShirtNameValue As String
Private playerSquadNumberValue As Integer
Private playerDOBValue As Date
Private playerPlaceOfBirthValue As String
Private playerNationalityValue As String
Private playerCategoryValue As Position
Private specialFeaturesValue As SpecialFeatures
Private teamValue As ObservableCollection(Of Team)
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property FirstName() As String
Get
Return Me.playerFirstNameValue
End Get
Set(ByVal value As String)
Me.playerFirstNameValue = value
OnPropertyChanged("FirstName")
End Set
End Property
Public Property Surname() As String
Get
Return Me.playerSurnameValue
End Get
Set(ByVal value As String)
Me.playerSurnameValue = value
OnPropertyChanged("Surname")
End Set
End Property
Public Property ShirtName() As String
Get
Return Me.playerShirtNameValue
End Get
Set(ByVal value As String)
Me.playerShirtNameValue = value
OnPropertyChanged("ShirtName")
End Set
End Property
Public Property SquadNumber() As Integer
Get
Return Me.playerSquadNumberValue
End Get
Set(ByVal value As Integer)
Me.playerSquadNumberValue = value
OnPropertyChanged("SquadNumber")
End Set
End Property
Public Property StartDate() As Date
Get
Return Me.playerDOBValue
End Get
Set(ByVal value As Date)
Me.playerDOBValue = value
OnPropertyChanged("DateofBirth")
End Set
End Property
Public Property PlaceOfBirth() As String
Get
Return Me.playerPlaceOfBirthValue
End Get
Set(ByVal value As String)
Me.playerPlaceOfBirthValue = value
OnPropertyChanged("Surname")
End Set
End Property
Public Property Nationality() As String
Get
Return Me.playerNationalityValue
End Get
Set(ByVal value As String)
Me.playerNationalityValue = value
OnPropertyChanged("Nationality")
End Set
End Property
Public Property Position() As Position
Get
Return Me.playerCategoryValue
End Get
Set(ByVal value As Position)
Me.playerCategoryValue = value
OnPropertyChanged("Position")
End Set
End Property
Public Property SpecialFeatures() As SpecialFeatures
Get
Return Me.specialFeaturesValue
End Get
Set(ByVal value As SpecialFeatures)
Me.specialFeaturesValue = value
OnPropertyChanged("SpecialFeatures")
End Set
End Property
Public ReadOnly Property Team() As ReadOnlyObservableCollection(Of Team)
Get
Return New ReadOnlyObservableCollection(Of Team)(Me.teamValue)
End Get
End Property
Public Sub New(ByVal FirstName As String, ByVal Surname As String, ByVal ShirtName As String, ByVal PlaceOfBirth As String, ByVal NationalityImageSourceString As String, ByVal Category As Position, ByVal squadNumber As Integer, ByVal DOB As Date, ByVal specialFeatures As SpecialFeatures)
Me.playerFirstNameValue = FirstName
Me.playerSurnameValue = Surname
Me.playerShirtNameValue = ShirtName
Me.playerPlaceOfBirthValue = PlaceOfBirth
Me.playerNationalityValue = Nationality
Me.playerDOBValue = DOB
Me.playerSquadNumberValue = squadNumber
Me.playerCategoryValue = Category
Me.specialFeaturesValue = specialFeatures
Me.teamValue = New ObservableCollection(Of Team)()
End Sub
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
End Class
Public Enum Position
GK
DF
MF
FW
End Enum
Public Enum SpecialFeatures
None
Yellow
Red
SubbedOff
SubbedOn
Highlight
End Enum
Data Template in Application.xaml:
<DataTemplate DataType="{x:Type src:Player}">
<Button Name="Player_Button" Style="{StaticResource Player}" Height="24" Width="320" Margin="0,2,0,0">
<Grid HorizontalAlignment="Center" Width="300">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"></ColumnDefinition>
<ColumnDefinition Width="210"></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Name="Player_Number_Text" Grid.Column="0" FontFamily="Font/#Gotham Narrow Bold" Text="{Binding Path=SquadNumber}" Margin="0,0,0,0" HorizontalAlignment="Left" />
<StackPanel Orientation="Horizontal" Grid.Column="1">
<TextBlock Name="Player_FirstName_Text" FontFamily="Font/#Gotham Narrow Bold" Text="{Binding Path=FirstName}" Margin="5,0,0,0" HorizontalAlignment="Left" />
<TextBlock Name="Player_Surname_Text" FontFamily="Font/#Gotham Narrow Bold" Text="{Binding Path=Surname}" Margin="5,0,0,0" HorizontalAlignment="Left" />
</StackPanel>
<Rectangle Name="Player_Card" Grid.Column="2"></Rectangle>
<TextBlock Name="Player_Position_Text" Grid.Column="3" FontFamily="Font/#Gotham Narrow Bold" Text="{Binding Path=Position}" Margin="5,0,0,0" />
<Image x:Name="Player_Nationality_Image" Margin="0,0,0,0" Grid.Column="4" Source="Images/Countries/Scotland.png" Height="14" Width="14" />
</Grid>
</Button>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=SpecialFeatures}">
<DataTrigger.Value>
<src:SpecialFeatures>Yellow</src:SpecialFeatures>
</DataTrigger.Value>
<DataTrigger.Setters>
<Setter Property="Fill" Value="Yellow" TargetName="Player_Card" />
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=SpecialFeatures}">
<DataTrigger.Value>
<src:SpecialFeatures>Highlight</src:SpecialFeatures>
</DataTrigger.Value>
<Setter Property="Fill" Value="Blue" TargetName="Player_Card" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
You'll need to implement an IValueConverter.
Somewhere in your solution add the following class. (I highly recommend making a converter folder and dumping all converters to be used throughout the application in this folder so you can find and modify them easily down the track.)
Imports System.ComponentModel
Imports System.Windows.Data
Imports System.Windows.Media
Public Class StringToImageConverter
Implements IValueConverter
Public Sub New()
End Sub
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object _
Implements IValueConverter.Convert
Select Case value
Case "Scotland"
'
Return New ImageSourceConverter().ConvertFromString("/YOURSOLUTION;component/Images/Countries/Scotland.png")
'
Case "Australia"
'
Return New ImageSourceConverter().ConvertFromString("/YOURSOLUTION;component/Images/Countries/Australia.png")
'
Case Else
Return Nothing
End Select
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object _
Implements IValueConverter.ConvertBack
End Function
End Class
In Application.xaml place the following code.
<Form.Resources>
<my:StringToImageConverter x:Key="StringToImageConverter" />
<Form.Resources>
<Button Name="Player_Button" Style="{StaticResource Player}" Height="24" Width="320" Margin="0,2,0,0">
<Grid HorizontalAlignment="Center" Width="300">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"></ColumnDefinition>
<ColumnDefinition Width="210"></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Name="Player_Number_Text" Grid.Column="0" FontFamily="Font/#Gotham Narrow Bold" Text="{Binding Path=SquadNumber}" Margin="0,0,0,0" HorizontalAlignment="Left" />
<StackPanel Orientation="Horizontal" Grid.Column="1">
<TextBlock Name="Player_FirstName_Text" FontFamily="Font/#Gotham Narrow Bold" Text="{Binding Path=FirstName}" Margin="5,0,0,0" HorizontalAlignment="Left" />
<TextBlock Name="Player_Surname_Text" FontFamily="Font/#Gotham Narrow Bold" Text="{Binding Path=Surname}" Margin="5,0,0,0" HorizontalAlignment="Left" />
</StackPanel>
<Rectangle Name="Player_Card" Grid.Column="2"></Rectangle>
<TextBlock Name="Player_Position_Text" Grid.Column="3" FontFamily="Font/#Gotham Narrow Bold" Text="{Binding Path=Position}" Margin="5,0,0,0" />
<Image Source="{Binding Path=Nationality, Converter={StaticResource StringToImageConverter}}" Stretch="None" />
</Grid>
Hope this helps.

WPF DataGridTemplateColumn Checkbox element IsChecked with Converter TwoWay binding

I have an DatagridTemplate Column containing an Checkbox which through an converter gets checked when a property on my ItemSource is "J" and unchecked when the property is "N".
This works, but now I want the property to be set to "J" if I select the checkbox or "N" when I deselect it.
My Column:
<local:JNConverter x:Key="JNConverter" />
<DataGridTemplateColumn Header="">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" HorizontalAlignment="Center">
<CheckBox Name="auto" HorizontalAlignment="center" IsChecked="{Binding Path=Autonummering, Converter={StaticResource JNConverter}, Mode=TwoWay}" />
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
My Converter:
Public Class JNConverter
Implements IValueConverter
Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
If value IsNot Nothing AndAlso value.ToString.ToLower = "j" Then
Return True
Else
Return False
End If
End Function
Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
If CType(value, Boolean) Then
Return "J"
Else
Return "N"
End If
End Function
End Class
My Itemsource is a List(Of Attribuut), Attribuut :
Public Class Attribuut
Inherits DependencyObject
Public Shared AutonummeringProperty As DependencyProperty = DependencyProperty.Register("Autonummering", GetType(String), GetType(Attribuut))
Public Property Autonummering As String
End Class
So how would I "reverse" bind the clicking on the checkbox to change the Autonummering property to "J" or "N"?
Thanks in advance
Set the UpdateSourceTrigger on the IsChecked-Binding to PropertyChanged, that should do it.

Using a Grid as an ItemsHost

I would like to use a Grid as an ItemsHost but none of the items appear in their bound (column, row) positions. How can I make this work? As a simple example:
XAML
<ItemsControl ItemsSource="{Binding DataSet}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Grid.Column="{Binding Col}" Grid.Row="{Binding Row}" Text="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Style>
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<Grid HorizontalAlignment="Stretch" IsItemsHost="True">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Style>
</ItemsControl>
Codebehind
Class Window1
Private myTestData As TestData
Public Sub New()
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
myTestData = New TestData()
Me.DataContext = myTestData
End Sub
End Class
Class TestData
Private myDataSet As List(Of DataPoint)
Public Property DataSet() As List(Of DataPoint)
Get
Return myDataSet
End Get
Set(ByVal value As List(Of DataPoint))
myDataSet = value
End Set
End Property
Public Sub New()
Me.DataSet = New List(Of DataPoint)
For x As Integer = 0 To 1
For y As Integer = 0 To 1
Me.DataSet.Add(New DataPoint(x, y, "Column " + x.ToString + ", Row " + y.ToString))
Next
Next
End Sub
End Class
Class DataPoint
Private myRow As Integer
Public Property Row() As Integer
Get
Return myRow
End Get
Set(ByVal value As Integer)
myRow = value
End Set
End Property
Private myCol As Integer
Public Property Col() As Integer
Get
Return myCol
End Get
Set(ByVal value As Integer)
myCol = value
End Set
End Property
Private myText As String
Public Property Text() As String
Get
Return myText
End Get
Set(ByVal value As String)
myText = value
End Set
End Property
Public Sub New(ByVal x As Integer, ByVal y As Integer, ByVal name As String)
Me.Row = y
Me.Col = x
Me.Text = name
End Sub
End Class
Because you're using an ItemsControl, a container is generated for each item. That container (an instance of ContentPresenter for a plain old ItemsControl) wraps the TextBlock, and is a direct child of the Grid. Therefore, the Grid never even sees the Column and Row properties on the TextBlock because it's looking instead at the container.
You can solve this by setting an ItemContainerStyle that binds the appropriate properties for the container:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Grid.Column" Value="{Binding Column}"/>
<Setter Property="Grid.Row" Value="{Binding Row}"/>
</Style>
</ItemsControl.ItemContainerStyle>
Possible workaround: if you use UniformGrid, you may not need to specify the rows and columns.

Resources