WPF INotifyPropertyChanged not updating label - wpf

I'm currently learning some basics in WPF and I've been looking for the mistake for about 2 days. Hope you guys can help.
I'm trying to update my UI (in this case the content of a label) by using INotifyPropertyChanged and a binding in XAML. The thing is: it only takes the first value and puts it in the content. Furthermore nothing happens but the event (OnPropertyChanged) is fired.
This is what I have in XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="MainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:View x:Key="ViewModel"/>
</Window.Resources>
<Grid Margin="0,0,2,-4" DataContext="{Binding Source={StaticResource ViewModel}}">
....
<Label x:Name="lbl_money" Grid.ColumnSpan="2" Content="{Binding Path=PropMoney}" HorizontalAlignment="Left" Margin="403,42,0,0" VerticalAlignment="Top">
And this is the necessary part of my class View:
Public Class View
Inherits ViewModelBase
Private rest1 As New Restaurant
Private mainPlayer As New Player
Private mycurrentMoney As Double = 3
Private currentClickIncrease = mainPlayer.PropClickIncrease
Public Property PropMoney() As Double
Get
Return mycurrentMoney
End Get
Set(value As Double)
mycurrentMoney = value
OnPropertyChanged("mycurrentMoney")
End Set
End Property
Sub SelfClicked()
PropMoney() += 1
End Sub
Last but not least the MainWindow class, where i instantiate my view:
Class MainWindow
Private view As New View
Sub New()
InitializeComponent()
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
view.SelfClicked()
End Sub
End Class
So my mycurrentMoney is increasing each click and the event is fired but the label doesn't update.
Thank you in advance!

If you have Visual Studio 15 use NameOf operator instead of string literal like so:
NameOf(PropMoney);
If you later rename your property, it will still work opposed to string literal which will NOT. Alternatively modify your OnPropertyChange to make use of CallerMemberName
OnPropertyChange ([System.Runtime.CompilerServices.CallerMemberName] string memberName = "")
{
}
The property name will be filled in, this works only in setter for current property however.
Also, set DataContext for whole window (Setting DataContext in XAML in WPF). DataContext={StaticResource ViewModel} and don't use Path in your Binding, just {Binding PropertyName}

Your OnPropertyChanged("mycurrentMoney") statement won't raise a property change on your property, because it's called PropMoney.
You have to set OnPropertyChanged("PropMoney") in your setter instead.

There are 2 problems with your code
First you raise PropertyChanged event for the backing field and should raise it for property name
OnPropertyChanged("PropMoney")
Second the property you change belong to different instance of View then the one set as DataContext. So in XAML remove DataContext changes, only leave property binding
<Window ...>
<Grid Margin="0,0,2,-4">
<!-- .... -->
<Label ... Content="{Binding Path=PropMoney}">
and then in code set DataContext of MainWindow to the instance that you create and modify
Class MainWindow
Private view As New View
Sub New()
InitializeComponent()
DataContext = view
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
view.SelfClicked()
End Sub
End Class

Related

UserControl Command to modify Property

In a User Control, I am trying to get a Command to modify a Property. I have an IncrementValueCommand and a Value property that I want to increment when a button is clicked. The button's Command is bound to the IncrementValueCommand and the Content is bound to the Value property.
I have tried two approaches to do this and in both cases the Button doesn't show the Value incrementing..
1st Approach: Dependency Property for Value
XAML:
<UserControl x:Class="UserControl1"
x:Name="root"
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:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="200"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Button Content="{Binding Path=Value}"
Command="{Binding Path=IncrementValueCommand}" />
</UserControl>
Code behind:
Public Class UserControl1
Public Shared ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Integer), GetType(UserControl1), New PropertyMetadata(1))
Public Property IncrementValueCommand As ICommand
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
IncrementValueCommand = New RelayCommand(AddressOf IncrementValue)
End Sub
Public Property Value() As Integer
Get
Return GetValue(ValueProperty)
End Get
Set(value As Integer)
SetValue(ValueProperty, value)
End Set
End Property
Private Sub IncrementValue()
Value += 1
End Sub
End Class
2nd approach: INotifyPropertyChanged for Value
XAML:
<UserControl x:Class="UserControl2"
x:Name="root"
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:local="clr-namespace:WpfApp1"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="200"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Button Content="{Binding Path=Value}"
Command="{Binding Path=IncrementValueCommand}" />
</UserControl>
Code behind:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Class UserControl2
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private _value As Integer = 1
Public Property IncrementValueCommand As ICommand
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
IncrementValueCommand = New RelayCommand(AddressOf IncrementValue)
End Sub
Public Property Value() As Integer
Get
Return _value
End Get
Set(value As Integer)
If _value <> value Then
_value = value
NotifyPropertyChanged()
End If
End Set
End Property
' This method is called by the Set accessor of each property.
' The CallerMemberName attribute that is applied to the optional propertyName
' parameter causes the property name of the caller to be substituted as an argument.
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private Sub IncrementValue()
Value += 1
End Sub
End Class
I left out the RelayCommand class which is a standard implementation for ICommand.
Any help will be greatly appreciated.
Working Code (thanks to Peter Duniho for his answer)
Adjust the code-behind constructor by creating the IncrementValueCommand first:
Public Sub New()
' Add any initialization after the InitializeComponent() call? Nah
IncrementValueCommand = New RelayCommand(AddressOf IncrementValue)
' This call is required by the designer.
InitializeComponent()
End Sub
As I've explained in this comment, the problem in this particular variant of your attempts to use a command to update a value is that you are initializing the IncrementValueCommand property after the call to InitializeComponent() in the class constructor.
The InitializeComponent() call is where the binding to that property is set up, i.e. Command="{Binding Path=IncrementValueCommand}" in your XAML. When that call is made, the property still has its default value of null.
When you assign the property a value later, because the property is an auto-implemented property, there's nothing about that assignment that would cause a property-change notification to happen, so the binding is never updated to reflect the new value.
You can either implement property-change notification for that property, just as is already done for the Value property, or you can (as I had suggested earlier) move the assignment within the constructor so that it occurs before the call to InitializeComponent instead of after.

WPF Combobox Item Update

I am rather new to the WPF setup and I am running into an issue where as far as I can see I have set it up correctly to have my combobox bound to a observable collection of object.
The Combobox will update when I add or delete items. If I make a change the items in the drop down will not show any differently but if I select one that was edited it will now show the new information but only when selected.
I have set up the object class to use INotifyPropertyChanged correctly I think but it does not seem to be functioning. Going to attach the code below so that you can easily see exactly what I am trying to describe.
What I am trying to do it allow a user to push a button and have the text inside a combobox update to show the new text.
Imports System.ComponentModel
Public Class Window2
Public _names As New System.Collections.ObjectModel.ObservableCollection(Of TestClass)
Public Sub BaseLoading() Handles MyBase.Loaded
Dim AddNewItem As New TestClass
AddNewItem.groupName = "Item " + (_names.Count + 1).ToString
_names.Add(AddNewItem)
cbo_Names.SetBinding(ItemsControl.ItemsSourceProperty, New Binding With {.Source = _names})
End Sub
Private Sub button_PreviewMouseDown(sender As Object, e As MouseButtonEventArgs)
Dim AddNewItem As New TestClass
AddNewItem.groupName = "Item " + (_names.Count + 1).ToString
_names.Add(AddNewItem)
_names(0).groupName = ("Value Changed")
End Sub
End Class
Public Class TestClasss
Implements INotifyPropertyChanged
Public _groupName As String = ""
Public Property groupName As String
Get
Return _groupName.ToString
End Get
Set(value As String)
_groupName = value
onPropertyChanged(New PropertyChangedEventArgs(_groupName))
End Set
End Property
Public Event PropertyChagned(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Public Sub onPropertyChanged(ByVal e As PropertyChangedEventArgs)
RaiseEvent PropertyChagned(Me, e)
End Sub
End Class
XAML
<Window x:Class="Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button x:Name="button" Content="Button" PreviewMouseDown="button_PreviewMouseDown"/>
<ComboBox x:Name="cbo_Names" Margin="30,5,30,5" IsEditable="False" ItemsSource="{Binding _names, NotifyOnSourceUpdated=True,Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="groupName" SelectedItem="{Binding _names, NotifyOnSourceUpdated=True,Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Window>
I would appreciate any help locating what I am missing.
You should pass the name of the data-bound property (instead of the value of the property) to the constructor of the PropertyChangedEventArgs:
onPropertyChanged(New PropertyChangedEventArgs("groupName"))
If you are using at least Visual Studio 2015, you could consider making the following change to your onPropertyChanged routine:
Public Sub onPropertyChanged(<System.Runtime.CompilerServices.CallerMemberName> Optional ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Then, in the setter for groupName you can call onPropertyChanged without specifying the property name, and it will be taken from the name of the caller (that is, it will end up being "groupName").
Effectively, this is doing the same thing as the previous answer, but in a way that is easier for you to code and maintain. (Along with the <CallerMemberName> attribute, this works well with NameOf, both making your code more robust against any changes in names of properties.)

WPF binding with .NET object not communicating the data

I am following a tutorial on WPF data binding. I am trying to bind to a .NET object's property to a XAML control but the control does not display the expected data. These are what I believe to be the relevant sections of code:
In procedural code: (Note: removed ObservableCollection in PhotoGallery after original post)
Namespace PhotoGallery
Partial Public Class MainWindow
Inherits Window
Private photos As New Photos
...
End Class
Namespace PhotoGallery
Public Class Photos
Inherits Collection(Of Photo)
...
End Class
In XAML (Solution/Project name is Ch13-PhotoGallery):
<Window x:Class="PhotoGallery.MainWindow"
...
xmlns:local="clr-namespace:Ch13_PhotoGallery.PhotoGallery"
...>
<Window.Resources>
<local:Photos x:Key="Photos"/>
</Window.Resources>
And this is the control that is not displaying the data, which is the size of the Photos collection:
<Label x:Name="numItemsLabel" Background="AliceBlue" FontSize="8" Content="{Binding Source={StaticResource Photos}, Path=Count}"/>
When I typed in the < Label >, Intellisense popped up 'Count' for the Path property, so I think that tells me I have everything defined correctly.
If I add this line of procedural code behind to the refresh() method:
numItemsLabel.Content = photos.Count
Then the count is displayed correctly.
But I'm not getting the binding in XAML to display Photos.Count.
This creates a new instance of the Photos class:
<local:Photos x:Key="Photos"/>
If you want to bind to the Photos collection that you have created in your MainWindow.xaml.vb file you should expose it as a public property - you can only bind to properties but not fields - and set the DataContext of the window to an instance of the class where this property is defined, i.e. the window class itself in your case:
Class MainWindow
Public Property Photos As Photos
Public Sub New()
' This call is required by the designer.
InitializeComponent()
DataContext = Me
...
End Sub
End Class
You can the bind directly to the property:
<Label x:Name="numItemsLabel" Background="AliceBlue" FontSize="8" Content="{Binding Path=Photos.Count}"/>
Your ViewModel needs to implement the INotifyPropertyChanged interface, this will let your window listen to changes to your ViewModel
Here's an example of how to implement this interface in VB
https://social.msdn.microsoft.com/Forums/vstudio/en-US/7de44362-8b88-4292-b4ee-0385c3b34d7d/im-just-looking-for-a-simple-vb-net-mvvm-sample-wpf?forum=wpf
ViewModel
Public Class ViewModel
Implements INotifyPropertyChanged
Public Sub New()
Me.myTextValue = "default value..."
End Sub
Private myTextValue As String = String.Empty
Public Property MyTextProperty() As String
Get
Return Me.myTextValue
End Get
Set(ByVal value As String)
Me.myTextValue = value
NotifyPropertyChanged("MyTextProperty")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
XAML
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox Text="{Binding MyTextProperty}"/>
</Grid>
</Window>
XAML Code Behind
Class MainWindow
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.DataContext = New ViewModel()
End Sub
End Class

Binding doesn't get called on code behind propertry

So I have a Grid with some checkboxes, etc inside it and wanted to set them all to readonly , I added the IsEnabled part below:
<Grid IsEnabled="{Binding IsFieldReadOny}">
And in the code behind added this:
Private _isFieldReadOnly As Boolean = True
Public Property IsFieldReadOny() As Boolean
Get
Return _isFieldReadOnly
End Get
Set(value As Boolean)
_isFieldReadOnly = value
End Set
End Property
But when I put breakpoint, it does not get hit or do anything.
If I manually hard code a True for the grid, then it works.
I am new to both WPF and VB syntax, so it might be something easy that I am not doing right.
Here is a very simple example of MVVM and binding with one way out of TONS to do things. Binding in and of itself has many many options of traversing a visual tree with 'RelativeSource' and scoping. As well as mode options and other settings. I chose to focus on keeping it simple though. I just want a view that has a textbox, you can change yourself, a button you can hit, a label that will update from the text you changed.
So here is a basic view:
<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:SimpleWPF"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBox Text="{Binding Text}" Height="30" />
<Button Content="Example" Command="{Binding DoCommand}" />
<Label Content="{Binding Output}" Height="30" />
</StackPanel>
</Window>
I want to set up a single helper class for a 'DelegateCommand'. You can do this many ways but essentially I am saving repeat methods for later reuse for commands to help with an ICommand interface.
Public Class DelegateCommand(Of T)
Implements ICommand
Private _execute As Action(Of T)
Public Sub New(execute As Action(Of T))
_execute = execute
End Sub
Public Event CanExecuteChanged As EventHandler
Private Event ICommand_CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
Private Function ICommand_CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Return True
End Function
Private Sub ICommand_Execute(parameter As Object) Implements ICommand.Execute
_execute.Invoke(DirectCast(parameter, T))
End Sub
End Class
Now in my Code behind of the view it should be pretty minimal except this:
Class MainWindow
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.DataContext = New MainViewModel()
End Sub
End Class
And my MainViewModel is pretty simple in this case is pretty simple and I am only implementing INotifyPropertyChanged. I would usually do most of my stuff like this in a base class and inherit that on larger solutions.
Imports System.ComponentModel
Public Class MainViewModel
Implements INotifyPropertyChanged
Private _text As String
Public Property Text As String
Get
Return _text
End Get
Set(ByVal value As String)
_text = value
OnPropertyChanged(NameOf(Text))
End Set
End Property
Private _output As String
Public Property Output As String
Get
Return _output
End Get
Set(ByVal value As String)
_output = value
OnPropertyChanged(NameOf(Output))
End Set
End Property
Public Sub New()
_text = "Test"
End Sub
Public ReadOnly Property DoCommand As New DelegateCommand(Of Object)(AddressOf DoIt)
Private Sub DoIt(obj As Object)
Output = $"{Text} {DateTime.Now.ToLongDateString}"
End Sub
#Region "Implement INotifyProperty Changed"
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
#End Region
End Class
When you use a generic binding you are looking at the DataContext of the object and by generic I mean a {Binding PropertyName} without any other parameters. In order to bind to a property within your code behind (which I don't recommend doing) then you need to tell the binding to look at that location. You also need to use a dependency property for binding on UIElements since it's already built in.
So to make this work I've named the Window the Grid is in 'window'. Then I've given the binding expression a direct connection to the property.
<Grid IsEnabled="{Binding IsReadOnlyField, ElementName=window}" />
I've then added a binding to the Checkbox as well to the same thing.
<CheckBox Content="Is Grid Enabled" IsChecked="{Binding IsReadOnlyField, ElementName=window}" />
Then in the code behind I've changed the property to a DependencyProperty.
public bool IsReadOnlyField
{
get { return (bool)GetValue(IsReadOnlyFieldProperty); }
set { SetValue(IsReadOnlyFieldProperty, value); }
}
public static readonly DependencyProperty IsReadOnlyFieldProperty =
DependencyProperty.Register(nameof(IsReadOnlyField), typeof(bool), typeof(MainWindow));
This will get the binding working.
If you are not using the code behind and are binding to a ViewModel or any class you should preferably make that class interface INotifyPropertyChanged (although you can also make that ViewModel inherit from DependencyObject and use the same DependencyPropery... It's just normally used for UI elements). Then write the property as normal and in the setter call the property changed event. However, you will most likely set the binding back to the way you had it and just put that ViewModel as the DataContext.
There's A LOT to explain about binding as it can be very flexible and used many different ways. Once you get it though you got it and learning more ways to bind will be simple. I suggest learning exactly how the binding takes place so that you can manipulate and choose the best binding for any situation.

WPF binded Listbox bug when display 2 items

I have a WPF listbox in my window. In the Load event of the window, i create a List(of Object) and I added some items. At application starts or debug, I can see items.
If I add 1 item on the list, i correctly see 1 only item. If I add 3 or more items, i correctly see 3 or more items. If I add 2 only items, i see 1 only item. Why?
Here is my WPF code
<Window x:Class="Cacatua.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cacatua;assembly=" >
<Grid>
<ListBox Name="lbSearch" ItemsSource="{Binding}" />
</Grid>
</Window>
And here is my code-behind (same assembly, in Cacatua namespace):
Private myLstSearch As List(Of Object)
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
myLstSearch = New List(Of Object)
lbSearch.ItemsSource = myLstSearch
Dim myMedia1 as Media1
myMedia1 = New Media1("IdMedia1-A")
myLstSearch.Add(myMedia1)
myMedia1 = New Media1("IdMedia1-B")
myLstSearch.Add(myMedia1)
End Sub
where Media1 is a simple class that contains a string
Public Class Media1
Private myIdTitolo As String
Public ReadOnly Property IDTitolo As String
Get
Return (myIdTitolo)
End Get
End Property
Public Sub New(str As String)
myIdTitolo = str
End Sub
End Class
With this code, I would see a list with this output (there is no datatemplate):
Cacatua.Media1
Cacatua.Media1
but I see only
Cacatua.Media1
I think it's a bug. But am I the first with this problem?
You've got the right idea, but the problem is your ItemsSource doesn't know when to update since you're not using an ObservableCollection. Also there is a timing issue between rendering and loading the window, and I think this has to do with the fact you aren't properly binding your items source.
For starters, try changing the type of myLstSearch to ObservableCollection(Of Media1).
Also, a better way to do this would be to databind it from the XAML directly, so your code-behind would be something like:
Public property MyListSearch As ObservableCollection(Of Media1)
Then your XAML would look like:
<Window x:Class="Cacatua.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Cacatua;assembly=">
<Grid>
<ListBox Name="lbSearch" ItemsSource="{Binding Path=MyListSearch}" />
</Grid>
</Window>
That way, you can simply initialize MyListSearch in your window constructor, and then add elements to it whenever, while your view will automatically update.

Resources