I have a treeview binded to an Observablecollection of ParentViewModel.
The ParentViewModel contains an Observablecollection of ChildViewModel.
Both this ViewModel implement an IsSelected property. I put it in both of them because the implementation is a little bit different. The program have to behave differently if I press a Parent or Child item.
My problem is that also if I select a Child item, program execute the IsSelected property of the ParentViewModel, not the one of the ChildViewModel.
This is my XAML:
<TreeView Name="MyTreeView"
ItemsSource="{Binding ParentList}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ParentViewModel}" ItemsSource="{Binding Childs}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding PictureString}" Height="32" Width="32" Margin="0,8,6,4" />
<TextBlock Grid.Column="1" Text="{Binding Name}" FontSize="15" Margin="10" />
</Grid>
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:ChildViewModel}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<!--<Image Grid.Column="0" Source="{Binding PictureString}" Height="32" Margin="0,8,6,4" />-->
<TextBlock Grid.Column="1" Text="{Binding Name}" Height="18" FontSize="15" Margin="10"/>
</Grid>
</DataTemplate>
</TreeView.Resources>
</TreeView>
The program never hit the IsSelected setter of the ChildViewModel.
Thanks in advance
EDIT
This is the BaseViewModel:
Public Class InheritableTreeViewItem
Implements INotifyPropertyChanged
Friend m_Name As String
Public Property Name As String
Get
Return m_Name
End Get
Set(value As String)
If (value <> m_Name) Then
m_Name = value
NotifyPropertyChanged("Name")
End If
End Set
End Property
Friend m_IsSelected As Boolean
Public Overridable Property IsSelected As Boolean
Get
Return m_IsSelected
End Get
Set(value As Boolean)
If (value <> m_IsSelected) Then
m_IsSelected = value
NotifyPropertyChanged("IsSelected")
End If
End Set
End Property
Private m_sPictureString As String
Private m_Items As ObservableCollection(Of InheritableTreeViewItem)
Public Property PictureString As String
Get
Return m_sPictureString
End Get
Set(value As String)
If value <> m_sPictureString Then
m_sPictureString = value
NotifyPropertyChanged("PictureString")
End If
End Set
End Property
Public Property Items As ObservableCollection(Of InheritableTreeViewItem)
Get
Return m_Items
End Get
Set(value As ObservableCollection(Of InheritableTreeViewItem))
m_Items = value
End Set
End Property
Sub New(Name As String)
Me.Name = Name
Me.Items = New ObservableCollection(Of InheritableTreeViewItem)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub NotifyPropertyChanged(propName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
End Sub
End Class
And this is the ParentViewModel:
Public Class ParentViewModel
Inherits InheritableTreeViewItem
Public Overrides Property IsSelected As Boolean
Get
Return m_IsSelected
End Get
Set(value As Boolean)
If (value <> m_IsSelected) Then
m_IsSelected = value
If value Then
' The function that I want to call When Parent item is selected in the tree
End If
NotifyPropertyChanged("IsSelected")
End If
End Set
End Property
Sub New(Name As String)
MyBase.New(Name)
Me.PictureString = "/Resources/TreeView/Folder.png"
End Sub
End Class
The ChildViewModel is basically identical, it differs only for the function that I call when it is selected. This function modify the content and visibility of some TextBoxes.
I think your problem is at this block :
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
Because it is a style, I'm not sure that the DataContext is properly inherited. Also why do you redefine this in the HierarchicalDataTemplate? I don't think you need to redefine this style for ChildViewModelItems.
Maybe you could debug your databinding in depth to see what's happening on this property : How to debug data binding.
Also, the plural of Child is Children, not Childs ;)
Related
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.
I'm creating a simple User Control combining a popup with a text view, nothing crazy. When I set it up in a window at first to style it all out, it worked perfectly, but when I moved it into a User Control to actually finish it up, it won't work correctly any more.
I pass a min and max value into the control and then it automatically creates a list of numbers to pick from in that range. In the User Control, the list of numbers doesn't get bound correctly, who knows why. Maybe someone can take a look at my code.
I've read a bunch of other questions about this, but don't really know what's happening. I'm not getting any errors in my output window, so no clues there. Anyway, here's the code -
UserControl.xaml
<UserControl x:Class="UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Me"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<TextBox Name="myTextbox"
Height="30"
Margin="0"
FontSize="14"
IsReadOnly="True"
Padding="5,2"
Text="{Binding Value}" />
<Popup x:Name="myPopup"
PlacementTarget="{Binding ElementName=myTextbox}"
StaysOpen="True">
<Popup.Style>
<Style TargetType="{x:Type Popup}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myTextbox, Path=IsFocused}" Value="True">
<Setter Property="IsOpen" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
<StackPanel>
<ListView Name="myListView"
Height="100"
MaxHeight="300"
ItemsSource="{Binding List,
UpdateSourceTrigger=PropertyChanged}"
SelectionChanged="ListView_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Label Width="100"
Height="30"
Margin="0"
Content="{Binding}"
FontFamily="Segoe UI"
FontSize="14"
Padding="5,2" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Width="200" Height="30" />
</StackPanel>
</Popup>
</StackPanel>
UserControl.xaml.vb
Imports System.ComponentModel
Imports System.Linq.Expressions
Imports System.Collections.ObjectModel
Class UserControl1
' Dependency Properties
Public Shared ReadOnly ListProperty As DependencyProperty = DependencyProperty.Register("List", GetType(ObservableCollection(Of Integer)), GetType(MainWindow))
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Integer), GetType(MainWindow))
Public Shared ReadOnly MaxValueProperty As DependencyProperty = DependencyProperty.Register("MaxValue", GetType(Integer), GetType(MainWindow))
Public Shared ReadOnly MinValueProperty As DependencyProperty = DependencyProperty.Register("MinValue", GetType(Integer), GetType(MainWindow))
' Properties
Public Property List As ObservableCollection(Of Integer)
Get
Return DirectCast(GetValue(ListProperty), ObservableCollection(Of Integer))
End Get
Set(value As ObservableCollection(Of Integer))
SetValue(ListProperty, value)
End Set
End Property
Public Property Value As Integer
Get
Return DirectCast(GetValue(ValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(ValueProperty, value)
End Set
End Property
Public Property MaxValue As Integer
Get
Return DirectCast(GetValue(MaxValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(MaxValueProperty, value)
End Set
End Property
Public Property MinValue As Integer
Get
Return DirectCast(GetValue(MinValueProperty), Integer)
End Get
Set(value As Integer)
SetValue(MinValueProperty, value)
End Set
End Property
Private Sub ListView_SelectionChanged(sender As System.Object, e As System.Windows.Controls.SelectionChangedEventArgs)
Value = List(myListView.SelectedIndex)
End Sub
Private Sub UserControl1_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
List = New ObservableCollection(Of Integer)
' Add all available numbers into the list
For iCounter As Integer = MinValue To MaxValue
List.Add(iCounter)
Next
' Set the selected index on the list for the value
myListView.SelectedIndex = Value - MinValue
End Sub
End Class
Just for reference, when I tested this out, I set the Min and Max values myself in both the window and usercontrol setup.
I think that you have made the same mistake that I used to when I first started learning WPF. I can't guarantee that this is the cause of your problem, but as it's the only one that I can see, I'll address it. Unfortunately, there are so many poor tutorials and quick examples that show the connecting of a UserControl.DataContext to itself:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Or:
DataContext = this;
Now this is perfectly acceptable if you don't want to Bind to the UserControl externally because it's a quick and easy way to connect with properties defined in the code behind. However, when you want to Bind to the properties from outside the control, you'll find problems. In these instances (if not on all occasions), you should use a RelativeSource Binding to Bind to your code behind properties:
<TextBox Name="myTextbox" Height="30" Margin="0" FontSize="14" IsReadOnly="True"
Padding="5,2" Text="{Binding Value, RelativeSource={RelativeSource AncestorType={
x:Type UserControl1}}}" />
In this Binding, UserControl1 is the name of the UserControl that declared the properties. This should be done on all of the internal Bindings. This way, the DataContext is not set to the UserControl, but the Bindings still find the properties.
You can find out more about RelativeSource from the RelativeSource MarkupExtension page on MSDN.
As I cant provide a comment to Sheridan good answer I have to provide a new answer, sorry for this.
While I love this solution
DataContext="{Binding RelativeSource={RelativeSource Self}}"
it fails (as Sheridan pointed out already) fast.
What you can do is just set the DataContext of the content of your User Control
<UserControl x:Class="Example.View.Controls.MyUserControl"
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"
xmlns:controls="clr-namespace:Example.View.Controls"
mc:Ignorable="d">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:MyUserControl}}}">
</Grid>
In this way all following Bindings have less Boilerplate code as you can just bind directly to your DP from the code-behind like:
<Label Content="{Binding MyLabel}"/>
As for Windows Apps (Windows 8 and Windows 10 UWP) the way to go is to give your control a name and reference it within your XAML file using Path and ElementName:
<UserControl
x:Class="MyControl"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="Control"
mc:Ignorable="d" >
<Grid Height="240" VerticalAlignment="Top">
<Rectangle Fill="{Binding ElementName=Control, Path=Background}" />
</Grid>
</UserControl>
``
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.
Silverlight's dataInput:Label and dataInput:DescriptionViewer controls provide a useful function through their ability to bind to a TextBox. Rather than hard-coding the text content of my labels and descriptions with System.ComponentModel.DataAnnotations.DisplayAttribute, I'd prefer to bind these attributes to properties of a class that holds admin-editable text. This works for Label, but not for DescriptionViewer.
Specifically, this Label works:
<dataInput:Label Grid.Row="00" Grid.Column="0"
Target="{Binding ElementName=inpStatus}"
Content="{Binding StatusLabel}" IsRequired="true" />
but this DescriptionViewer is invisible:
<dataInput:DescriptionViewer Grid.Row="00" Grid.Column="3"
Target="{Binding ElementName=inpStatus}"
Name="dvBound2" Description="{Binding Path=StatusDescription}" />
When I remove the binding to my TextBox, the DescriptionViewer is shown:
<dataInput:DescriptionViewer Grid.Row="00" Grid.Column="2"
Name="dvBound1" Description="{Binding Path=StatusDescription}" />
Should it be possible to bind a DescriptionViewer to both a TextBox and an alternate Description source?
Thanks in advance!
MainPage.xaml:
<UserControl x:Class="DataInputDemo.MainPage"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controlsToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="142"></ColumnDefinition>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="24"></ColumnDefinition>
<ColumnDefinition Width="24"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
</Grid.RowDefinitions>
<dataInput:Label Grid.Row="00" Grid.Column="0"
Target="{Binding ElementName=inpStatus}"
Content="{Binding StatusLabel}" IsRequired="true" />
<TextBox Grid.Row="00" Grid.Column="1"
Text="{Binding Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True, Path=Status}"
Name="inpStatus" MaxLength="025"
BindingValidationError="_BindingValidationError" />
<dataInput:DescriptionViewer Grid.Row="00" Grid.Column="2"
Name="dvBound1" Description="{Binding Path=StatusDescription}" />
<!-- Following DescriptionViewer is not shown. -->
<dataInput:DescriptionViewer Grid.Row="00" Grid.Column="3"
Target="{Binding ElementName=inpStatus}"
Name="dvBound2" Description="{Binding Path=StatusDescription}" />
<dataInput:ValidationSummary Grid.Row="01" Grid.Column="0"
Grid.ColumnSpan="4" Name="VS1" />
</Grid>
</UserControl>
MainPage.xaml.vb:
Partial Public Class MainPage
Inherits UserControl
Public myDataClass = New DataClass
Public Sub New()
InitializeComponent()
myDataClass.status = "Default Status"
myDataClass.StatusLabel = "Status Label"
myDataClass.StatusDescription = "Status Description"
LayoutRoot.DataContext = myDataClass
End Sub
Private Sub _BindingValidationError(ByVal sender As Object, ByVal e As ValidationErrorEventArgs)
End Sub
End Class
DataClass.vb:
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Runtime.Serialization
Imports System.ComponentModel.DataAnnotations
Imports System.Windows.Controls
Public Class DataClass
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Function ValidateEntry(ByVal fieldname As String, ByRef value As Object) As Object
Dim ctx As New ValidationContext(Me, Nothing, Nothing)
ctx.MemberName = fieldname
Validator.ValidateProperty(value, ctx)
Return value
End Function
<DataMember()> _
<Required()> _
<StringLength(100)> _
Public Property Status() As String
Get
Return _Status
End Get
Set(ByVal value As String)
_Status = ValidateEntry("Status", value)
NotifyPropertyChanged("")
End Set
End Property
Private _Status As String
<DataMember()> _
Public Property StatusLabel() As String
Get
Return _StatusLabel
End Get
Set(ByVal value As String)
_StatusLabel = value
NotifyPropertyChanged("")
End Set
End Property
Private _StatusLabel As String
<DataMember()> _
Public Property StatusDescription() As String
Get
Return _StatusDescription
End Get
Set(ByVal value As String)
_StatusDescription = value
NotifyPropertyChanged("")
End Set
End Property
Private _StatusDescription As String
End Class
I had the exact same problem with the DescriptionViewer using this markup:
<TextBox x:Name="UserNameTextBox" Text="{Binding UserName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true, UpdateSourceTrigger=Explicit}" Grid.Column="1" Grid.Row="1"></TextBox>
<dataInput:DescriptionViewer Target="{Binding ElementName=UserNameTextBox}" Description="{Binding Path=CreateUserResources.UserNameDescription, Source={StaticResource GlobalResources}}" Grid.Column="2" Grid.Row="1"></dataInput:DescriptionViewer>
Before binding the Description to its resource it was a plain string in the markup and it did work.
To resolve the problem I have set the PropertyPath of the DescriptionViewer with the property name where the binding is located on the Target control. It is now working correctly.
<TextBox x:Name="UserNameTextBox" Text="{Binding UserName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true, UpdateSourceTrigger=Explicit}" Grid.Column="1" Grid.Row="1"></TextBox>
<dataInput:DescriptionViewer Target="{Binding ElementName=UserNameTextBox}" PropertyPath="Text" Description="{Binding Path=CreateUserResources.UserNameDescription, Source={StaticResource GlobalResources}}" Grid.Column="2" Grid.Row="1"></dataInput:DescriptionViewer>
If you are using MVVM make sure the PropertyPath does not match a property of your presenter model. I did not investigate but I had an instance where I was forced to change the property name of my presenter to make it work. Seems like the control was trying to parse the metadata from the property of my presenter instead of the target control.
Hope this help.
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.