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

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.

Related

How do I add, save and load items to a listview in WPF

About two weeks ago I started to develop in WPF and, since I only developed in WinForms, I ran into common problems but managed to find the solution for them. However, currently I'm stuck at something: adding items with multiple columns (via visual basic code, not xaml) into a listview.
I'm not sure if it's best to use a Listview or a DataGrid control for this but basically I want the user to be able to add favourite songs to a list, save them to a file and load the file whenever the app is opened. The listview currently has three columns: Artist, Song and Status.
When I was programming in WinForms, I used to do this:
Dim Song As New ListViewItem
Form1.ListView1.Items.Add(Song)
Song.Text = TextBox1.Text
Song.SubItems.Add(TextBox2.Text)
Song.SubItems.Add(TextBox3.Text)
Then, to save:
Dim Song As New ListViewItem
Form1.ListView1.Items.Add(Song)
Song.Text = TextBox1.Text
Song.SubItems.Add(TextBox2.Text)
Song.SubItems.Add(TextBox3.Text)
Try
Dim myWriter As New IO.StreamWriter(PATH_DATABASE)
For Each myItem As ListViewItem In Form1.ListView1.Items
myWriter.WriteLine(myItem.Text & "|" & myItem.SubItems(1).Text & "|" & myItem.SubItems(2).Text & "|" & myItem.SubItems(3).Text
Next
myWriter.Close()
Catch ex As Exception
MsgBox("Error: " & ex.Message, vbCritical, "Error")
End Try
I've been searching and I've found that I need to use binding so I've tried this:
Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Windows
Public Structure Song
Public _artist As String
Public _title As String
Public _status As String
Property Artist() As String
Get
Return _artist
End Get
Set(ByVal Value As String)
_artist = Value
End Set
End Property
Property Title() As String
Get
Return _title
End Get
Set(ByVal Value As String)
_title = Value
End Set
End Property
Property Status() As String
Get
Return _status
End Get
Set(ByVal Value As String)
_status = Value
End Set
End Property
End Structure
Public Class WINDOW_AddSong
Dim songs As New ObservableCollection(Of Song)
Private Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click
Dim Song As New ListViewItem
For Each wnd As Window In Application.Current.Windows
If wnd.GetType Is GetType(MainWindow) Then
DirectCast(wnd, MainWindow).Listview1.Items.Add(Alimento)
Alimento.Content = New Song() With {._artist = "Lol", ._title = "Lol2", ._status = "Lol3"}
End If
Next
End Sub
End Class
And in the XAML listview:
<GridViewColumn Header="Artist" DisplayMemberBinding="{Binding Artist}"/>
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title}"/>
<GridViewColumn Header="Status" DisplayMemberBinding="{Binding Status}"/>
This works however I'm not sure if this is the WPF-way of doing things.
However, I'm stuck at the saving process:
Try
Dim myWriter As New IO.StreamWriter(PATH_DATABASE)
For Each wnd As Window In Application.Current.Windows
If wnd.GetType Is GetType(MainWindow) Then
For Each myItem As ListViewItem In DirectCast(wnd, MainWindow).Listview1.Items
myWriter.WriteLine(Song, Artist, Status)
Next
End If
Next
myWriter.Close()
Catch ex As Exception
MsgBox("Error: " & ex.Message, vbCritical, "Error")
End Try
This doesn't work.
Also, PATH_DATABASE is just a directory.
To summarise, could an expert review my code and check if I'm doing things "right" and, if possible, could you help me with the saving and loading process?
Thanks.
I just updated your structure (I used a class, just force of habit) to use automatic property values which are available in recent versions of VS. The default implementation of properties is built in along with the backer fields. It is used just as before.
You are on the right track with ObservableCollection but you you need to implement INotifyPropertyChanged by adding the Event PropertyChanged and providing the Sub OnPropertyChanged to raise the event.
Set up the bindings on the controls and set the ItemsSource ot the ListView to your ObservableList.
To save Import System.Text so you can use a StringBuilder. The loop through the list to build the string to save to a text file.
I didn't spend any time trying to make the XAML Windows look nice. Pardon the ugliness.
Class MainWindow
Public Songs As New ObservableCollection(Of Song)
Protected Sub OnLoad(sender As Object, e As RoutedEventArgs)
Songs.Add(New Song("Nirvana", "Smells Like Teen Spirit", "Open"))
Songs.Add(New Song("John Lennon", "Imagine", "In Stock"))
Songs.Add(New Song("U2", "One", "Unknown"))
Songs.Add(New Song("Michael Jackson", "Billie Jean", "Open"))
lvSongs.ItemsSource = Songs
End Sub
Private Sub BtnAdd_Click(sender As Object, e As RoutedEventArgs)
Dim frm As New frmAdd
frm.mainForm = Me 'send the instance of the current form to the new form
frm.ShowDialog()
End Sub
Private Sub SaveList()
Dim sb As New StringBuilder
For Each item As Song In Songs
sb.AppendLine($"{item.Artist}|{item.Title}|{item.Status}")
Next
'call sb.ToString and write it to a .txt file
End Sub
End Class
MainWindow 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPF_BindComboBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Loaded="OnLoad"
Name="root">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListView Margin="10" Name="lvSongs" Grid.Row="0">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBox x:Name="txtArtist"
Text="{Binding Artist, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
FontWeight="Bold"/>
<TextBlock Text=", " />
<TextBlock Text="Age: " />
<TextBox Text="{Binding Title}"
FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBox Text="{Binding Status}" />
<TextBlock Text=")" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="btnAdd" Grid.Row="1" Height="25" Width="100" Content="Add a Song" Click="BtnAdd_Click"/>
</Grid>
</Window>
The Song class
Public Class Song
Implements INotifyPropertyChanged
Public Property Artist() As String
Public Property Title() As String
Public Property Status() As String
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
Public Sub New(art As String, Ttl As String, Stat As String)
Artist = art
Title = Ttl
Status = Stat
End Sub
End Class
The Add Window
Public Class frmAdd
Public mainForm As MainWindow
Private Sub BtnAdd_Click(sender As Object, e As RoutedEventArgs)
mainForm.Songs.Add(New Song(txtArtist.Text, txtTitle.Text, txtStatus.Text))
ClearForm()
End Sub
Private Sub ClearForm()
txtArtist.Clear()
txtTitle.Clear()
txtStatus.Clear()
End Sub
End Class
Add Window XAML
<Window x:Class="frmAdd"
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:local="clr-namespace:WPF_BindComboBox"
mc:Ignorable="d"
Title="frmAdd" Height="172.641" Width="307.547">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Artist" FontSize="16" FontWeight="Bold"/>
<TextBox x:Name="txtArtist" Grid.Column="1" Grid.Row="0" FontSize="16" Width="150" />
<TextBlock Grid.Column="0" Grid.Row="1" Text="Title" FontSize="16" FontWeight="Bold"/>
<TextBox x:Name="txtTitle" Grid.Column="1" Grid.Row="1" FontSize="16" Width="150"/>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Status" FontSize="16" FontWeight="Bold"/>
<TextBox x:Name="txtStatus" Grid.Column="1" Grid.Row="2" FontSize="16" Width="150"/>
<Button x:Name="btnAdd" Grid.Column="1" Grid.Row="4" Content="Add Song" Click="BtnAdd_Click"/>
</Grid>
</Window>
EDIT
Required Imports
Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Text

DBContext.ChangeTracker not detecting changes to an Array

My EF6 project in VB.NET maps to a MySQL database using the following mapping:
Partial Public Class WorkRule
Public Property WorkRuleID As Integer 'Maps to int
Public Property Description As String 'Maps to tinytext
Public Property RegularTime As Byte() 'Maps to tinyblob
End Class
If I edit the Description textbox, changes are detected and saved as expected upon calling .SaveChanges. However, if I edit the content of one of the textboxes bound to an array element, the bound element is changed but the change is not detected by the DBContext.ChangeTracker and the changes are not saved to the database upon calling .SaveChanges.
I can force a save by manually setting the DBContext.Entry().State=EntityState.Modified but is there a better way to do this so changes to the array elements are automatically detected? Should I be using something other than an array perhaps?
Code follows:
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox x:Name="lstWorkRules"/>
<StackPanel Grid.Column="1" DataContext="{Binding ElementName=lstWorkRules, Path=SelectedItem}">
<Label>Description:</Label>
<TextBox Text="{Binding Path=Description}"/>
<Label>Day 1:</Label>
<TextBox Text="{Binding Path=RegularTime[0]}"/>
<Label>Day 2:</Label>
<TextBox Text="{Binding Path=RegularTime[1]}"/>
<Label>Etc.</Label>
<Button Click="Button_Click">Save</Button>
</StackPanel>
</Grid>
VB:
Imports System.Data.Entity
Class MainWindow
Dim wrContext As WorkRuleEntities
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
wrContext = New WorkRuleEntities
wrContext.WorkRules.Load
lstWorkRules.ItemsSource = wrContext.WorkRules.Local
lstWorkRules.DisplayMemberPath = "Description"
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
wrContext.SaveChanges()
End Sub
End Class

Treeview IsSelected with different viewmodel

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

Auto Update of Values in UI WPF form

I have a class as follows:
Public Class BillAmounts
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChange(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Private _LabourCostValue As Double
Private _TransportPriceValue As Double
Private _ItemsTotalCost_ As Double
Private _FinalPriceValue As Double
Property TransportPrice As Double
Get
Return _TransportPriceValue
End Get
Set(ByVal value As Double)
If Not _TransportPriceValue = value Then
_TransportPriceValue = value
_FinalPriceValue = TransportPrice + LabourCost + ItemsTotalCost_
PriceCalculationNotification()
End If
End Set
End Property
Property LabourCost As Double
Get
Return _LabourCostValue
End Get
Set(ByVal Value As Double)
If Not _LabourCostValue = Value Then
_LabourCostValue = Value
_FinalPriceValue = TransportPrice + LabourCost + ItemsTotalCost_
PriceCalculationNotification()
End If
End Set
End Property
Property ItemsTotalCost_ As Double
Get
Return _ItemsTotalCost_
End Get
Set(ByVal value As Double)
If Not _ItemsTotalCost_ = value Then
_ItemsTotalCost_ = value
FinalPrice = TransportPrice + LabourCost + ItemsTotalCost_
'NotifyPropertyChange("ItemsTotalCost_")
PriceCalculationNotification()
End If
End Set
End Property
ReadOnly Property TotalPrice As Double
Get
Try
Return ItemsTotalCost_ + TransportPrice + LabourCost
Catch ex As Exception
Return 0
End Try
End Get
'Set(ByVal value As Double)
' If Not _TotalpriceValue = value Then
' _TotalpriceValue = value
' NotifyPropertyChange("TotalPrice")
' End If
'End Set
End Property
Property FinalPrice As Double
Get
Return _FinalPriceValue
End Get
Set(ByVal value As Double)
If Not _FinalPriceValue = value Then
_FinalPriceValue = value
PriceCalculationNotification()
End If
End Set
End Property
ReadOnly Property Discount_ As Double
Get
'_Discount_ = FinalPrice - TotalPrice
'Return _Discount_
Return FinalPrice - TotalPrice
End Get
End Property
Public Sub New()
_TransportPriceValue = 0
_LabourCostValue = 0
_ItemsTotalCost_ = 0
_FinalPriceValue = 0
End Sub
Private Sub PriceCalculationNotification()
NotifyPropertyChange("TransportPrice")
NotifyPropertyChange("LabourCost")
NotifyPropertyChange("Discount_")
NotifyPropertyChange("TotalPrice")
NotifyPropertyChange("FinalPrice")
End Sub
End Class
I have the bound the fields as follows:
<StackPanel Name="AmountStack" Orientation="Vertical" >
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition Width="30"/>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="Transport" Grid.Column="0" Grid.Row="0" VerticalAlignment="Center" />
<TextBox Text="{Binding TransportPrice}" Name="TransportTxtBox" Grid.Column="1" Grid.Row="0" VerticalAlignment="Center" />
<TextBlock Text="Labour" Grid.Column="3" Grid.Row="0" VerticalAlignment="Center" />
<TextBox Text="{Binding LabourCost}" Name="labourTxtBox" Grid.Column="4" Grid.Row="0" VerticalAlignment="Center" />
<TextBlock Text="Total Amount =" VerticalAlignment="Center" Grid.Column="0" Grid.Row="1"/>
<TextBox Text="{Binding TotalPrice}" Name="TotalTextBox" IsReadOnly="True" Grid.Column="1" Grid.Row="1" />
<TextBlock Text="Discount= " VerticalAlignment="Center" Grid.Column="3" Grid.Row="1"/>
<TextBox Text="{Binding Path=Discount_, Mode=OneWay}" IsReadOnly="True" Name="DiscountTextBox" Grid.Column="4" Grid.Row="1" />
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,5,0,0">
<TextBlock Text="Total Amount = " VerticalAlignment="Center" />
<TextBox Text="{Binding Path=FinalPrice}" Name="FinalTotalTextBox" Width="130" />
</StackPanel>
</StackPanel>
and
AmountStack.DataContext = Bill.Amounts
However, the issue is that TOtalAMount is getting updated automatically but FinalPrice is not getting updated. Any reasons/mistakes I have done. I have tried it a lot in few ways but could not get that working. What i did not understand is that Total amount is getting updated but Final price isn't. Thank you.
Do not use Private _FinalPriceValue to assign value in Setter TransportPrice and LabourCost. Use FinalPrice to raise PriceCalculationNotification() everytime properly.
I do not remember the source but the point is when there is read only property the binding does not work correctly some times and is waiting for a fix in next release, I solved by manually coding for change by text change events.

Binding Silverlight 3 description attribute of dataInput:DescriptionViewer

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.

Resources