I have created a wpf vb.net project and am trying to set a simple databing. I'm not quiet sure how to set my DataContext = this; in the codebind. Currently when i run the program my Label never is updated. I have included my code below. What I'm i missing?
<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>
<Label Content="{Binding person.Name}"/>
</Grid>
</Window>
Class MainWindow
Private Property person As New Person()
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
person.Name = "Poco"
End Sub
End Class
System.ComponentModel
Public Class Person
Implements INotifyPropertyChanged
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
OnPropertyChanged(New PropertyChangedEventArgs("Name"))
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(ByVal e As PropertyChangedEventArgs)
If Not PropertyChangedEvent Is Nothing Then
RaiseEvent PropertyChanged(Me, e)
End If
End Sub
End Class
This is close - you need to name your label in the XAML (so that you can reference it from the code behind) and then specify the path within the bound object to the data you would like to bind. In this case you will be binding an object with a Name property whose content you would like to assign to the label text:
<Label Name="MyLabel" Content="{Binding Path = Name}"/>
and then in your code you need to set the DataContext of the label to the object you would like to have it bound to, in this case a specific instance, somePerson of the class Person :
Private somePerson As New Person
Public Sub New()
InitializeComponent()
MyLabel.DataContext = somePerson
somePerson.Name = "Poco"
End Sub
Related
My project is in.NET 6. Why doesn't the following code work?
My understanding is to produce code through attributes, simplifying the coding.
Did I do something wrong?
Xaml:
<Window.DataContext>
<self:MainWindowVM />
</Window.DataContext>
<StackPanel >
<Label Content="{Binding Path=FirstLabel ,Mode=TwoWay ,UpdateSourceTrigger=PropertyChanged }" Height="50" />
<Button Command="{Binding Path=ExitMe}" Content="Exit" />
</StackPanel>
Codebedhing:
Imports CommunityToolkit.Mvvm.ComponentModel
Imports CommunityToolkit.Mvvm.Input
Class MainWindow
End Class
Partial Public Class MainWindowVM
Inherits ObservableObject
Public Sub New()
Me.FirstLabel = "default value..."
End Sub
<ObservableProperty>
Private FirstLabel As String
<RelayCommand>
Private Sub ExitMe()
Windows.Application.Current.Shutdown()
End Sub
End Class
At run-time I get two error messages:
ExitMe' property not found on 'object' ''MainWindowVM'
BindingExpression path error: 'FirstLabel' property not found on 'object' ''MainWindowVM'
I know the following code will work. But why doesn't the above code work?
Imports CommunityToolkit.Mvvm.ComponentModel
Imports CommunityToolkit.Mvvm.Input
Class MainWindow
End Class
Partial Public Class MainWindowVM
Inherits ObservableObject
Public Sub New()
Me.FirstLabel = "default value..."
Me.ExitMe = New RelayCommand(AddressOf exMe)
End Sub
<ObservableProperty>
Private fLabel As String
Public Property FirstLabel As String
Private Sub Save()
Me.fLabel = FirstLabel
End Sub
<RelayCommand>
Private Sub exMe()
Windows.Application.Current.Shutdown()
End Sub
Public ReadOnly Property ExitMe As ICommand
End Class
I am still learning how to use INotifyPropertyChanged. I was able to see it work with a non-persisted list in this post. I am now trying to use an SQL persisted list.
XAML. Some of the unused grid space was for comparisons to the non-persisted list, but not relevant to this post's question.
<StackPanel Grid.Column="0">
<Button Margin="25" Content="Insert an item" Click="InsertAnItem_Click"/>
</StackPanel>
<StackPanel Grid.Column="1">
<Button Margin="25" Content="Clear stored list" Click="ClearStoredList_Click"/>
<Label Content="{}{Binding MyList2}"/>
<ListBox Name="listBox3" ItemsSource="{Binding MyList2}" DisplayMemberPath="Name" Height="100">
</ListBox>
<Label Content="updated with MyList2 using code behind"/>
<ListBox Name="listBox4" DisplayMemberPath="Name" Height="100">
</ListBox>
</StackPanel>
CodeBehind
Partial Class MainWindow
Private vm = New ViewModel
Sub New()
InitializeComponent()
DataContext = vm
For Each item In vm.MyList2
listBox4.Items.Add(item)
Next
End Sub
Private Sub InsertAnItem_Click(sender As Object, e As RoutedEventArgs)
vm.InsertAnItem()
listBox4.Items.Clear()
For Each item In vm.MyList2
listBox4.Items.Add(item)
Next
End Sub
Private Sub ClearStoredList_Click(sender As Object, e As RoutedEventArgs)
vm.ClearStoredList()
End Sub
End Class
ViewModel
Public Class ViewModel
Implements INotifyPropertyChanged
Public Property dbList As New Mvvm2DbContext
Public Property MyList2 As New List(Of MyListItem)
Public Sub New()
MyList2 = dbList.SqlStoredList.ToList()
End Sub
Public Sub InsertAnItem()
Dim anItem As New MyListItem
anItem.Name = "Item " & MyList2.Count()
MyList2.Add(anItem)
dbList.SqlStoredList.Add(anItem)
dbList.SaveChanges()
NotifyPropertyChanged("MyList2")
End Sub
Public Sub ClearStoredList()
Using db = New Mvvm2DbContext
Dim allItems = db.SqlStoredList
Try
db.SqlStoredList.RemoveRange(allItems)
Catch ex As Exception
Dim s = ex.Message
MessageBox.Show(s)
End Try
db.SaveChanges()
End Using
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Public Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Model
Public Class MyListItem
Public Property MyListItemID() As Integer
Get
Return _MyListItemID
End Get
Set(ByVal value As Integer)
_MyListItemID = value
End Set
End Property
Private _MyListItemID As Integer
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
Private _Name As String
Public Property Desc() As String
Get
Return _Desc
End Get
Set(ByVal value As String)
_Desc = value
End Set
End Property
Private _Desc As String
End Class
dbContext
Public Class Mvvm2DbContext
Inherits DbContext
Public Sub New()
MyBase.New("name=MVVM2DbConnection")
End Sub
Public Property SqlStoredList As DbSet(Of MyListItem)
End Class
My line NotifyPropertyChanged("MyList2") in the ViewModel doesn't cause the bound list to update.
I think the MyList2 definition in the ViewModel must be incorrect, but I don't understand how to make it different if that's the problem.
What is the proper code to get changes to percolate to the display? As always, if there are best practices I'm missing, please advise.
Updated after initial post but questions still remain:
The following changes appear to cause INotifyPropertyChanged to now work in this code. I'm guessing INotifyPropertyChanged doesn't work unless a property is defined with specific get/set declarations.
ViewModel. The MyList2 definition is changed and New() is changed.
Public Property MyList2() As ObservableCollection(Of MyListItem)
Get
Return _MyList2
End Get
Set(ByVal value As ObservableCollection(Of MyListItem))
_MyList2 = value
End Set
End Property
Private _MyList2 As New ObservableCollection(Of MyListItem)
Public Sub New()
For Each item In dbList.SqlStoredList
MyList2.Add(item)
Next
End Sub
The reason I previously defined MyList2 as:
Public Property MyList2 As New List(Of MyListItem)
Was because in New() initializing MyList2 with this line wouldn't compile:
MyList2 = dbList.SqlStoredList.ToList()
Even if I tried a DirectCast, List(Of T) can't be converted to an ObservableCollection(Of T). I'm guessing that's because of all the 'plumbing' behind the scenes?
Anyway, INotifyPropertyChanged seems to work fine now. But, I would appreciate any instruction regarding mistakes I'm making implementing this simple persisted list with INotifyPropertyChanged.
However, the persisted list is not fully bound. MyList2 doesn't clear when the SQL database is cleared. It seems to me that there must be more to this because changes to the SQL database could be made by other processes, and I don't see how this code would know that.
What do I do to ensure all updates get reflected in the UI?
The problem with your code is that the property MyList2 is not changing...its contents are changing. So that is why the property notification on MyList2 does nothing...MyList2 is still MyList2.
What you need is a collection class that implements notification. ObservableCollection would fit this need. So if you simply replace List(Of MyListItem) with ObservableCollection(Of MyListItem) I think things will start working.
I have a simple Button user Control that I use to aggregate all button functionality within my code:
Partial Public Class CommandButton
Inherits UserControl
Implements INotifyPropertyChanged
Public ReadOnly ButtonCmdProperty As DependencyProperty = DependencyProperty.Register("ButtonCmd", GetType(ICommand), GetType(CommandButton), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnButtonCmdChanged)))
Public ReadOnly ButtonTextProperty As DependencyProperty = DependencyProperty.Register("ButtonText", GetType(String), GetType(CommandButton), New PropertyMetadata("Undefined", New PropertyChangedCallback(AddressOf OnButtonTextChanged)))
Public Sub New()
InitializeComponent()
End Sub
#Region "ButtonCmd"
Public Property ButtonCmd As ICommand
Get
Return DirectCast(GetValue(ButtonCmdProperty), ICommand)
End Get
Set(value As ICommand)
SetValue(ButtonCmdProperty, value)
End Set
End Property
Private Sub OnButtonCmdChanged()
OnPropertyChanged("ButtonCmd")
End Sub
#End Region
#Region "ButtonText"
Public Property ButtonText() As String
Get
Return DirectCast(GetValue(ButtonTextProperty), String)
End Get
Set(value As String)
SetValue(ButtonTextProperty, value)
End Set
End Property
Private Sub OnButtonTextChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
OnPropertyChanged("ButtonText")
End Sub
#End Region
End Class
Here is an example of where I use it in XAML code:
<controls:CommandButton x:Name="btnEdit"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="100"
Grid.Column="0"
ButtonCmd="{Binding EditButton.ButtonCmd}"
ButtonText="{Binding EditButton.ButtonText, Mode=TwoWay}"
ButtonTooltip="{Binding EditButton.ButtonTooltip}"
IconHeight="{Binding EditButton.IconHeight}"
IconWidth="{Binding EditButton.IconWidth}"
IconSource="{Binding EditButton.IconSource}"
IsVisible="{Binding EditButton.IsVisible}"
ShowIcon="{Binding EditButton.ShowIcon}"
ShowText="{Binding EditButton.ShowText}" />
(As you can see there are many other properties, but I have omitted them for brevity sake.)
I have been working with the same code for over 2 years in VS2010 with no problems. Since upgrading to VS2013, every page where I use this button throws the error message:
Object of Type System.Windows.Data.BindingExpression cannot be converted to type System.String
on each binding. It doesn't appear to affect the way the user control works when running, but I can no longer see canvas when writing my XAML. (IE. the XAML won't render in the canvas area) I am pretty sure I have the dependency properties written correctly, but I thought I'd throw it out here to see if I am missing something or if there is a way to disable this error check.
Can anyone suggest what might be missing from the following code. I am getting nothing displayed on my WinForm.
I have looked at the following related posts on this topic but they haven't solved the issue for me.
How to use Prisim within an ElementHost
Problems with Prism hosted in a WinForm ElementHost
WinForm with ElementHost
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' WPF application instance
_wpfApplication = Application.Current()
If Application.Current Is Nothing Then
_wpfApplication = New Application()
End If
' Load the application modules
Dim unityBootstrapper As New Bootstrapper()
unityBootstrapper.Run()
' Bind the shell to the ElementHost
Dim shellElement = unityBootstrapper.Container.Resolve(Of Shell)()
ehMaster.Child = shellElement
End Sub
Bootstrapper
Public NotInheritable Class Bootstrapper
Inherits UnityBootstrapper
Protected Overrides Function CreateShell() As DependencyObject
Return New Shell
End Function
Protected Overrides Function GetModuleCatalog() As IModuleCatalog
Dim catalog As ModuleCatalog = New ConfigurationModuleCatalog()
catalog.AddModule(GetType(Pitchbook.Excel.ChartWizardModule))
Return catalog
End Function
End Class
Shell
<UserControl x:Class="Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.codeplex.com/CompositeWPF">
<DockPanel>
<ItemsControl x:Name="HeaderRegion" cal:RegionManager.RegionName="HeaderRegion" DockPanel.Dock="Top" Height="auto">
</ItemsControl>
<ItemsControl x:Name="LeftRegion" cal:RegionManager.RegionName="LeftRegion" Width="auto" DockPanel.Dock="Left">
</ItemsControl>
<ItemsControl x:Name="MainRegion" cal:RegionManager.RegionName="MainRegion" DockPanel.Dock="Bottom">
</ItemsControl>
</DockPanel>
</UserControl>
Partial Public Class Shell
Inherits UserControl
Public Sub New()
InitializeComponent()
End Sub
End Class
ChartWizardModule
Public NotInheritable Class ChartWizardModule
Implements IModule
Private ReadOnly regionManager As IRegionManager
Public Sub Initialize() Implements IModule.Initialize
regionManager.RegisterViewWithRegion("MainRegion", GetType(Test))
End Sub
Public Sub New(regionManager As IRegionManager)
Me.regionManager = regionManager
End Sub
End Class
I didn't pay enough attention to this post
How to use Prisim within an ElementHost
The issue is that you need an Application.Current that is not null and is not equal to typeof(Application).
Therefore I created the following class
Public Class WpfApplicationAdapter
Inherits Windows.Application
' HACK - This class shouldn't be required. Look out for a resolution to this in future WPF versions.
End Class
And changed my WinForm constructor to the following
Public Sub New()
' This call is required by the designer.
InitializeComponent()
If Application.Current Is Nothing Then
Dim wpfAppAdapter As New WpfApplicationAdapter
End If
' Load the application modules
Dim formBootstrapper As New Bootstrapper
formBootstrapper.Run()
' Get the current instance of shell and bind it to the ElementHost
Dim shellElement = formBootstrapper.Container.Resolve(Of Shell)()
ehMaster.Child = shellElement
End Sub
I may be confused about the purpose behind INotifyPropertyChanged and Silverlight...
I have XAML that I created in the designer:
<UserControl.Resources>
<CollectionViewSource x:Key="theViewSource" d:DesignSource="{d:DesignInstance my:transferObject, CreateList=True}" />
</UserControl.Resources>
as well as this
<Canvas DataContext="{StaticResource theViewSource}">
<sdk:DataGrid ItemsSource="{Binding}" Name="theDataGrid">
<sdk:DataGrid.Columns>
<!-- columns omitted from post -->
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Canvas>
And I also have some codebehind:
Private Sub control_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
If Not (System.ComponentModel.DesignerProperties.GetIsInDesignMode(Me)) Then
Dim myCollectionViewSource As System.Windows.Data.CollectionViewSource =
CType(Me.Resources("theViewSource"), System.Windows.Data.CollectionViewSource)
Dim myViewModel As New ViewModels.ViewModelThatHasAnObservableCollectionProperty()
myCollectionViewSource.Source = myViewModel.TheObservableCollection()
End If
End Sub
And a ViewModel that looks somewhat like this:
Public Class ViewModelBase
Implements ComponentModel.INotifyPropertyChanged
Protected Sub RaisePropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
Public Class ViewModelThatHasAnObservableCollectionProperty
Inherits ViewModelBase
Public Sub RetrieveCollectionCompletedHandler(ByVal sender As Object, ByVal e As SilverlightConsumableService.RetrieveMyCollectionCompletedEventArgs)
Dim unobservable As List(Of TransferObjects.TheTransferObject) = e.Result
'I was hoping for this next line of code to force a refresh of the DataGrid
TheObservableCollection = New ObservableCollection(Of TransferObjects.transferObject)(unobservable)
End Sub
Public Sub New()
Dim theClient As New SilverlightConsumableService.SilverlightConsumableSoapClient
AddHandler theClient.RetrieveMyCollectionCompleted, AddressOf RetrieveCollectionCompletedHandler
theClient.RetrieveMyCollectionAsync()
End Sub
Private _theObservableCollection As ObservableCollection(Of TransferObjects.transferObject)
Public Property TheObservableCollection() As ObservableCollection(Of TransferObjects.transferObject)
Get
Return _theObservableCollection
End Get
Set(ByVal value As ObservableCollection(Of TransferObjects.transferObject))
_theObservableCollection = value
RaisePropertyChanged("TheObservableCollection")
End Set
End Property
End Class
The result is that Silverlight renders the datagrid with no results because TheObservableCollection hasn't been populated by the event handler yet. That's to be expected. What I was hoping for was that after the asychronous call to the web service, the Setter for TheObservableCollection gets called, and subsequently so does RaisePropertyChanged.
My expectation was that Silverlight would handle this by rebinding the dataGrid to the property... Isn't the purpose behind using INotifyPropertyChanged to make it so that I don't have to rebind the grid programmatically, or am I confused about that?
In Silverlight (unlike WPF), you don't assign the ObservableCollection in your service response handler. This will break the binding.
Instead, clear out the already bound collection and re-populate it with the items from your service response. Updating the collection will properly raise the notification events. If you re-assign the collection's reference, the binding to the UI element will be broken.
The purpose behind INotifyPropertyChanged is to inform any bound members that the value of the property changed. In the case of collections, re-assigning the reference does not count as a value change. Updating the number of collection members does. Likewise, changing individual properties will raise the notify events as long as you call the PropertyChanged event. The purpose of the interface is to provide a contract for the View to listen to the ViewModel.