MVVM Bindings doesnt update with PropertyChanged - wpf

All my bindings are not updated and I don't understand why
My Model is:
Public Class TestModel
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyProperyChange(ByVal Info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Info))
End Sub
Private m_vorname As String
Public Property vorname() As String
Get
Return m_vorname
End Get
Set(ByVal value As String)
If m_vorname <> value Then
m_vorname = value
NotifyProperyChange("vorname")
MessageBox.Show("Vorname wurde zu:" + m_vorname)
End If
End Set
End Property
Public Sub wertverandern()
vorname = "Lukas"
End Sub
End Class
My View is the MainView with this settings:
Imports System.ComponentModel
Class MainWindow
Private WithEvents m_Person As New TestModel
Public Sub New()
' Dieser Aufruf ist für den Designer erforderlich.
InitializeComponent()
Me.DataContext = New TestModel()
' Fügen Sie Initialisierungen nach dem InitializeComponent()-Aufruf hinzu.
End Sub
Private Sub btn_Test_Click(sender As Object, e As RoutedEventArgs) Handles btn_Test.Click
m_Person.vorname = "MOINSEN"
End Sub
Private Sub m_Person_PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Handles m_Person.PropertyChanged
MessageBox.Show("Ausgeloest:" + m_Person.vorname, e.PropertyName)
End Sub
Private Sub gelicked(sender As Object, e As RoutedEventArgs) Handles btn_XXX.Click
m_Person.wertverandern()
End Sub
End Class
My XAML look like this:
<Grid>
<StackPanel Orientation="Horizontal">
<Button Name="btn_Test" Height="100" Width="100" VerticalAlignment="Center" HorizontalAlignment="Center" Content="1"/>
<Button Name="btn_XXX" Height="100" Width="100" VerticalAlignment="Center" HorizontalAlignment="Center" Content="{Binding vorname}" Margin="20"/>
<Label VerticalAlignment="Top" Background="Red" Width="100" Height="40" Foreground="Black" Content="{Binding vorname, FallbackValue=GEHT_NICHT, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Grid>

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.

Why this DropdownMenu doesn't work in WPF

I'm trying to make a DropdownMenu in WPF with the code-behind in VB. For any reason that I can't realise this DropdownMenu is not working as I would like to.
This is the MainWindow.xaml file:
<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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" Height="600" Width="1080" Foreground="White" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" WindowStyle="None">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<materialDesign:ColorZone Mode="PrimaryMid" Grid.ColumnSpan="2" HorizontalAlignment="Stretch">
<Grid>
<materialDesign:PopupBox PlacementMode="BottomAndAlignRightEdges" HorizontalAlignment="Right" Margin="10"/>
</Grid>
</materialDesign:ColorZone>
<Grid HorizontalAlignment="Stretch" Grid.Row="1" Background="{StaticResource PrimaryHueMidBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="326*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Background="GhostWhite">
<Image Source="Assets/logo.png"/>
</Grid>
<ScrollViewer HorizontalAlignment="Stretch" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" Grid.Row="1">
<StackPanel x:Name="Menu" Margin="10"/>
</ScrollViewer>
</Grid>
</Grid>
</Window>
This is the MainWindow.xaml.vb file:
Imports MaterialDesignThemes.Wpf
Imports Project.ViewModel
Partial Public Class MainWindow
Inherits Window
Public Sub New()
InitializeComponent()
Dim menuRegister = New List(Of SubItem)()
menuRegister.Add(New SubItem("Customer"))
menuRegister.Add(New SubItem("Providers"))
menuRegister.Add(New SubItem("Employees"))
menuRegister.Add(New SubItem("Products"))
Dim item6 = New ItemMenu("Register", menuRegister, PackIconKind.Register)
Dim menuSchedule = New List(Of SubItem)()
menuSchedule.Add(New SubItem("Services"))
menuSchedule.Add(New SubItem("Meetings"))
Dim item1 = New ItemMenu("Appointments", menuSchedule, PackIconKind.Schedule)
Dim menuReports = New List(Of SubItem)()
menuReports.Add(New SubItem("Customers"))
menuReports.Add(New SubItem("Providers"))
menuReports.Add(New SubItem("Products"))
menuReports.Add(New SubItem("Stock"))
menuReports.Add(New SubItem("Sales"))
Dim item2 = New ItemMenu("Reports", menuReports, PackIconKind.FileReport)
Dim menuExpenses = New List(Of SubItem)()
menuExpenses.Add(New SubItem("Fixed"))
menuExpenses.Add(New SubItem("Variable"))
Dim item3 = New ItemMenu("Expenses", menuExpenses, PackIconKind.ShoppingBasket)
Dim menuFinancial = New List(Of SubItem)()
menuFinancial.Add(New SubItem("Cash flow"))
Dim item4 = New ItemMenu("Financial", menuFinancial, PackIconKind.ScaleBalance)
Dim item0 = New ItemMenu("Dashboard", New UserControl(), PackIconKind.ViewDashboard)
Menu.Children.Add(New UserControlMenuItem(item0))
Menu.Children.Add(New UserControlMenuItem(item6))
Menu.Children.Add(New UserControlMenuItem(item1))
Menu.Children.Add(New UserControlMenuItem(item2))
Menu.Children.Add(New UserControlMenuItem(item3))
Menu.Children.Add(New UserControlMenuItem(item4))
End Sub
End Class
This is the ItemMenu Class:
Imports MaterialDesignThemes.Wpf
Namespace ViewModel
Public Class ItemMenu
Public Sub New(ByVal header As String, ByVal subItems As List(Of SubItem), ByVal icon As PackIconKind)
header = header
subItems = subItems
icon = icon
End Sub
Public Sub New(ByVal header As String, ByVal screen As UserControl, ByVal icon As PackIconKind)
header = header
screen = screen
icon = icon
End Sub
Public Property Header As String
Public Property Icon As PackIconKind
Public Property SubItems As List(Of SubItem)
Public Property Screen As UserControl
End Class
End Namespace
This is the SubItem Class:
Namespace ViewModel
Public Class SubItem
Public Sub New(ByVal name As String, ByVal Optional screen As UserControl = Nothing)
name = name
screen = screen
End Sub
Public Property Name As String
Public Property Screen As UserControl
End Class
End Namespace
This is the UserControlMenuItem.xaml file:
<UserControl x:Class="UserControlMenuItem"
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" >
<Grid>
<materialDesign:PackIcon Kind="{Binding Path=Icon}" Width="15" Height="15" Margin="10 16" Foreground="White"/>
<ListBoxItem x:Name="ListViewItemMenu" Content="{Binding Path=Header}" Padding="37 14" FontSize="15" Foreground="White"/>
<Expander x:Name="ExpanderMenu" Header="{Binding Path=Header}" IsExpanded="False" Width="210" HorizontalAlignment="Right" Background="{x:Null}" Foreground="White">
<ListView x:Name="ListViewMenu" ItemsSource="{Binding Path=SubItems}" Foreground="White" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" Padding="20 5"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</Grid>
This is the UserControlMenuItem.xaml.vb file:
Imports Project.ViewModel
Partial Public Class UserControlMenuItem
Inherits UserControl
Public Sub New(ByVal itemMenu As ItemMenu)
InitializeComponent()
ExpanderMenu.Visibility = If(itemMenu.SubItems Is Nothing, Visibility.Collapsed, Visibility.Visible)
ListViewItemMenu.Visibility = If(itemMenu.SubItems Is Nothing, Visibility.Visible, Visibility.Collapsed)
Me.DataContext = itemMenu
End Sub
End Class
This is the Window when the app is running:
What I'm missing here?
Design cloned from DropDownMenu
I solved this problem. It was a detail I didn't realise. These are the corrected Classes:
The ItemMenu Class:
Imports MaterialDesignThemes.Wpf
Namespace ViewModel
Public Class ItemMenu
Public Sub New(ByVal header As String, ByVal subItems As List(Of SubItem), ByVal icon As PackIconKind)
Me.Header = header
Me.SubItems = subItems
Me.Icon = icon
End Sub
Public Sub New(ByVal header As String, ByVal screen As UserControl, ByVal icon As PackIconKind)
Me.Header = header
Me.Screen = screen
Me.Icon = icon
End Sub
Public Property Header As String
Public Property Icon As PackIconKind
Public Property SubItems As List(Of SubItem)
Public Property Screen As UserControl
End Class
End Namespace
The SubItem Class:
Namespace ViewModel
Public Class SubItem
Public Sub New(ByVal name As String, ByVal Optional screen As UserControl = Nothing)
Me.Name = name
Me.Screen = screen
End Sub
Public Property Name As String
Public Property Screen As UserControl
End Class
End Namespace
Just remained make reference to the Class with "Me" (self in other languages like Python) and then assign a value to the property:
Me.Header = header 'Example in ItemMenu Class
Me.Name = name 'Example in SubItem Class

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

Enable ValidationRule if checkbox is checked

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.

Read MP3 Tag attribute in Silverlight

I know it works to read out the MP3 Tag attribute MP3V1 in silverlight. I know it works without any third party moduls. But when I use the MP3 workflow like Microsoft described I always get the count of zero attributes in my MP3 file.
How to solve?
Example project:
Download MP3 demo file from here:
http://www.file-upload.net/download-2617219/Elenor_techno.mp3.html
<UserControl x:Class="MP3_Tag_test.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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White" Margin="12">
<StackPanel Orientation="Vertical">
<MediaElement x:Name="MediaHelper" Visibility="Visible" />
<TextBlock x:Name="txtTitle" Text="Title" FontSize="28" />
<Button Content="STOP" Height="23" HorizontalAlignment="Left" Name="Button3" Width="75" />
<Button Content="Play MP3" Height="23" HorizontalAlignment="Left" Name="Button1" VerticalAlignment="Top" Width="75" Margin="0,20,0,0" />
<Button Content="Play stream" Height="23" HorizontalAlignment="Left" Name="Button2" VerticalAlignment="Top" Width="75" />
</StackPanel>
</Grid>
Partial Public Class MainPage
Inherits UserControl
Public Sub New()
InitializeComponent()
End Sub
Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
End Sub
Private Sub MediaHelper_CurrentStateChanged(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles MediaHelper.CurrentStateChanged
If Me.MediaHelper.Attributes.ContainsKey("Title") Then Me.txtTitle.Text = Me.MediaHelper.Attributes("Title")
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
Me.txtTitle.Text = "Loading mp3..."
Me.MediaHelper.Source = New Uri("http://dave-d.ch/database/music/public/1000036869_stefan%20lange%20koolja.mp3")
Me.MediaHelper.Play()
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button2.Click
Me.txtTitle.Text = "Loading stream..."
Me.MediaHelper.Source = New Uri("http://asx.skypro.ch/radio/internet-128/fm1-nord.asx")
Me.MediaHelper.Play()
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button3.Click
Me.MediaHelper.Stop()
End Sub
End Class
Ok. I created my own class and now I am submitting the MP3 Tag via service to Silverlight page. But I am still interested by Silverlight Tag reader does not work.

Resources