why <ObservableProperty> and <RelayCommand> do not work? - vb.net

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

Related

How do I get INotifyPropertyChanged to update bound items from a list stored in SQL?

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.

X:Bind datatemplate to event

I'm tryin to call a method in my model class, from my datatemplate but doesn't seem to compile. Gives error "Object reference not set to an instance of an object."
On pointerPressed I want to call method "Poke" using X:bind in my lvitem call.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView Grid.Column="0" ItemsSource="{x:Bind m_ViewModel.ListItems}" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:lvitem">
<TextBlock PointerPressed="{x:Bind Poke , Mode=OneWay }"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Grid.Column="1" Text="{x:Bind m_ViewModel._Name, Mode=TwoWay}"/>
</Grid>
MyViewModel
Imports Windows.UI
Namespace MyViewModel
Public Class MyViewModel
Implements INotifyPropertyChanged
Private Sub NotifyPropertyChanged(Optional propertyName As String = "")
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Name As String
Private List_Items As New ObservableCollection(Of lvitem)
Public Property _Name As String
Get
Return Name
End Get
Set(value As String)
Name = value
NotifyPropertyChanged()
End Set
End Property
Public Sub New()
Name = "Cater"
Dim lvi As New lvitem
Dim lvi2 As New lvitem
List_Items.Add(lvi)
List_Items.Add(lvi2)
End Sub
Public Property ListItems As ObservableCollection(Of lvitem)
Get
Return List_Items
End Get
Set(value As ObservableCollection(Of lvitem))
List_Items = value
End Set
End Property
Public Sub ClickMe()
_Name = "Clicked"
End Sub
End Class
End Namespace
CodeBehind
Public NotInheritable Class MainPage
Inherits Page
Public Property ViewModel() As MyViewModel.MyViewModel
Get
Return m_ViewModel
End Get
Set
m_ViewModel = Value
End Set
End Property
Private m_ViewModel As MyViewModel.MyViewModel
Public Sub New()
Me.InitializeComponent()
ViewModel = New MyViewModel.MyViewModel
End Sub
Private Sub Rectangle_PointerPressed(sender As Object, e As PointerRoutedEventArgs)
End Sub
Private Sub TextBlock_PointerPressed(sender As Object, e As PointerRoutedEventArgs)
End Sub
End Class
Lvitem Class - Model
Public Class lvitem
Public Sub New()
End Sub
Public Sub Poke(sender As Object, e As Object)
Dim meint As Integer
meint = 3
End Sub
End Class
Gives error "Object reference not set to an instance of an object."
Yes, we have found some issues for x:bind with x:DataType in DataTemplate.
Here are some points we need to take care for now:
Ensure having a Panel to contain elements in the DataTemplate
For TextBlock control, if we need to make the event x:bind binding works, the Text property must be set x:bind binding.
I simplify your repro demo and standardize some naming issues:
lvitem.vb:
Public Class lvitem
Public Sub Poke(sender As Object, e As PointerRoutedEventArgs)
Dim meint As Integer
meint = 3
End Sub
Private _Name As String
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
End Class
MyViewModel.vb:
Imports Windows.UI
Namespace MyViewModel
Public Class MyViewModel
Implements INotifyPropertyChanged
Private Sub NotifyPropertyChanged(Optional propertyName As String = "")
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private _Name As String
Public List_Items As New ObservableCollection(Of lvitem)
Public Property Name As String
Get
Return _Name
End Get
Set(value As String)
_Name = value
NotifyPropertyChanged()
End Set
End Property
Public Sub New()
Name = "Cater"
Dim lvi As New lvitem With {.Name = "Item1"}
Dim lvi2 As New lvitem With {.Name = "Item2"}
List_Items.Add(lvi)
List_Items.Add(lvi2)
End Sub
End Class
End Namespace
View:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView Margin="0,100,0,0" Grid.Column="0" ItemsSource="{x:Bind m_ViewModel.List_Items}" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:lvitem">
<Grid>
<TextBlock PointerPressed="{x:Bind Poke, Mode=OneWay}" Text="{x:Bind Name}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Margin="0,100,0,0" Grid.Column="1" Text="{x:Bind m_ViewModel.Name, Mode=TwoWay}"/>
</Grid>
Please also note that the PointerPressed event won't be triggered, the reason is in here: Can't fire PointerPressed event from a Listbox in WinRT

Object of Type `System.Windows.Data.BindingExpression` cannot be converted to type `System.String`

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.

How do I databind a property to a textbox in Wpf

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

Using Prism in ElementHost

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