How to bind button to method in VB.net - wpf

I am doing some research online to find out how to do this but I have fallen short so far. I think there is a knowledge gap that I have to overcome.
I have a wpf button that when clicked would execute a sub inside of the object it is bound too.
I would like to know how to get the button to execute exampleObject.displayMessage().
VB.NET
Private Sub Window_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
Dim exObject As New exampleObject
Grid1.DataContext = exObject
End Sub
Public Class exampleObject
Public ReadOnly Property testMessage As String
Get
Return "this is a test"
End Get
End Property
Public Sub displayMessage()
MsgBox(testMessage)
End Sub
End Class
WPF
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid Name="Grid1">
<Button Content="Button" Name="Button1"/>
</Grid>
</Grid>
</Window>

To execute a method you need to use Commands. Refer below implementation.
<Grid>
<Grid>
<Grid Name="Grid1">
<Button Content="Button" Name="Button1" Command="{Binding TestCommand}"/>
</Grid>
</Grid>
</Grid>
Class MainWindow
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
Dim exObject As New exampleObject
Grid1.DataContext = exObject
End Sub
End Class
Public Class exampleObject
Private m_TestCommand As ICommand
Public Property TestCommand As ICommand
Get
Return m_TestCommand
End Get
Set(ByVal value As ICommand)
m_TestCommand = value
End Set
End Property
Public Sub New()
m_TestCommand = New DelegateCommand(AddressOf displayMessage, AddressOf CandisplayMessage)
End Sub
Public Sub displayMessage(ByVal param As Object)
MsgBox(testMessage)
End Sub
Private Function CandisplayMessage(ByVal param As Object) As Boolean
Return True
End Function
Public ReadOnly Property testMessage As String
Get
Return "this is a test"
End Get
End Property
End Class
Public Class DelegateCommand
Implements ICommand
Private m_canExecute As Func(Of Object, Boolean)
Private m_executeAction As Action(Of Object)
Private m_canExecuteCache As Boolean
Public Event CanExecuteChanged(ByVal sender As Object, ByVal e As System.EventArgs) Implements ICommand.CanExecuteChanged
Public Sub New(ByVal executeAction As Action(Of Object), ByVal canExecute As Func(Of Object, Boolean))
Me.m_executeAction = executeAction
Me.m_canExecute = canExecute
End Sub
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
Dim temp As Boolean = m_canExecute(parameter)
If m_canExecuteCache <> temp Then
m_canExecuteCache = temp
RaiseEvent CanExecuteChanged(Me, New EventArgs())
End If
Return m_canExecuteCache
End Function
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
m_executeAction(parameter)
End Sub
End Class
I have got the Delegate command from http://www.paulspatterson.com/mvvm-and-wpf-for-vb-net-%E2%80%93-part-5-%E2%80%93-delegating-commands/. That site gives very detailed information.

Related

Can't declare the viewmodel in XAML for this code

I've reviewed as many 'how to declare the viewmodel in XAML' posts I can and still can't figure this out. I'm using the simple program below to learn the basics regarding binding and this code works. When I click to insert(add) items, the listbox automatically reflects that change, as well as when I clear the list.
See my question(s) after the code.
Model
Namespace MVVM3
Public Class MyListItem
Public Property MyListItemID() As Integer
Public Property Name() As String
End Class
End Namespace
ViewModel
Namespace MVVM3
Public Class ViewModel
Implements INotifyPropertyChanged
Public Property allIdeas() As New ObservableCollection(Of MyListItem)
Public Sub New()
For index = 0 To 9
Dim anItem As New MyListItem
anItem.Name = "Idea " & index
allIdeas.Add(anItem)
Next
End Sub
Public Sub InsertAnItem()
Dim anItem As New MyListItem
anItem.Name = "Item " & allIdeas.Count()
allIdeas.Add(anItem)
NotifyPropertyChanged("allIdeas")
End Sub
Public Sub ClearStoredList()
allIdeas.Clear()
NotifyPropertyChanged("allIdeas")
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
End Namespace
View
<Window x:Class="MVVM3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:mvvm3"
Title="MainWindow" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen"
>
<StackPanel Grid.Column="1">
<Button Margin="25" Content="Insert an item" Click="InsertAnItem_Click"/>
<Button Margin="25" Content="Clear stored list" Click="ClearStoredList_Click"/>
<ListBox Name="listBox3" ItemsSource="{Binding allIdeas}" DisplayMemberPath="Name" Height="100">
</ListBox>
</StackPanel>
</Window>
Code Behind
Namespace MVVM3
Partial Class MainWindow
Private vm = New ViewModel
Sub New()
InitializeComponent()
DataContext = vm
End Sub
Private Sub InsertAnItem_Click(sender As Object, e As RoutedEventArgs)
vm.InsertAnItem()
End Sub
Private Sub ClearStoredList_Click(sender As Object, e As RoutedEventArgs)
vm.ClearStoredList()
End Sub
End Class
End Namespace
I want to move the ViewModel declaration into XAML to eliminate code behind. If I comment out DataContext = vm, no matter what method I've followed from various posts, the binding no longer updates the listbox.
The following changes result in the listbox showing the initial assignment that takes place in ViewModel.New, but after that no changes are reflected:
<Window x:Class="MVVM3.MainWindow"
...
xmlns:local="clr-namespace:mvvm3"
xmlns:vm="clr-namespace:mvvm3.MVVM3"
...
>
<Window.DataContext>
<vm:ViewModel/>
</Window.DataContext>
What am I missing? Is it a namespace problem?
I'm hoping to move on to Commands and ViewModel locators, but I don't see how I can do that until I understand this.
When you declare an instance of the ViewModel class in XAML, you may access it in code behind by casting the value of the DataContext property to the ViewModel type:
Partial Class MainWindow
Sub New()
InitializeComponent()
End Sub
Private Sub InsertAnItem_Click(sender As Object, e As RoutedEventArgs)
CType(DataContext, ViewModel).InsertAnItem()
End Sub
Private Sub ClearStoredList_Click(sender As Object, e As RoutedEventArgs)
CType(DataContext, ViewModel).ClearStoredList()
End Sub
End Class

Why My MVVM does not work when I change data on the fly?

My 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:TestMetroChartsVB"
xmlns:chart="clr-namespace:GravityApps.Mandelkow.MetroCharts;assembly=GravityApps.Mandelkow.MetroCharts"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:TestPageViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<chart:ClusteredBarChart ChartTitle="Example1 " ChartSubTitle="Test1">
<chart:ClusteredBarChart.Series>
<chart:ChartSeries SeriesTitle="Errors" ItemsSource="{Binding Errors}"
DisplayMember="Category" ValueMember="Number"/>
</chart:ClusteredBarChart.Series>
</chart:ClusteredBarChart>
<Button x:Name="btnTest" Height="31" Margin="0,0,405,0" Content="Change Data"/>
</StackPanel>
</Grid>
</Window>
My view:
Imports GravityApps.Mandelkow.MetroCharts Class MainWindow
Private Sub btnTest_Click(sender As Object, e As RoutedEventArgs) Handles btnTest.Click
Dim test As New TestPageViewModel
test.changeData()
End Sub End Class
My viewModel:
Imports System.Collections.ObjectModel
Public Class TestPageViewModel
Public Property Errors() As ObservableCollection(Of TestClass)
Get
Return m_Errors
End Get
Private Set
m_Errors = Value
End Set
End Property
Private m_Errors As ObservableCollection(Of TestClass)
Public Sub New()
Errors = New ObservableCollection(Of TestClass)
Errors.Add(New TestClass("Data1", 5))
Errors.Add(New TestClass("Data2", 10))
Errors.Add(New TestClass("Data5", 15))
End Sub
Public Sub changeData()
Errors.Clear()
Errors.Add(New TestClass("DAta9", 10))
End Sub
End Class
My Model:
Public Class TestClass
Public Property Category As String
Get
Return m_Category
End Get
Set(value As String)
m_Category = value
End Set
End Property
Private m_Category As String
Public Property Number As Integer
Get
Return m_Number
End Get
Set(value As Integer)
m_Number = value
End Set
End Property
Private m_Number As Integer
Public Sub New(category As String, number As Integer)
Me.Category = category
Me.Number = number
End Sub End Class
It displays data initially like this:
But when I click my "change data" button data does not change?
What's the problem?
You are changing the wrong data:
Private Sub btnTest_Click(sender As Object, e As RoutedEventArgs) Handles btnTest.Click
Dim test As New TestPageViewModel
test.changeData()
End Sub
In your button's Click event handler, you create a new view model object and call the changeData() on it. But that's not the view model object the view is using, nor do you do anything to make it the view model object the view is using.
The best thing would be to just get the actual view model and change that one:
Private Sub btnTest_Click(sender As Object, e As RoutedEventArgs) Handles btnTest.Click
Dim view As Button = CType(sender, Button)
Dim test As TestPageViewModel = CType(view.DataContext, TestPageViewModel)
test.changeData()
End Sub
The above assumes that the Button sending the Click event has inherited the MainWindow's data context.

Do you need all those properties in MVVM?

I recently started using MVVM and just figured out how to work with Commands with the help of http://www.dotmaniac.net/wpf-karl-shifletts-relaycommand/ and http://www.codeproject.com/Articles/126249/MVVM-Pattern-in-WPF-A-Simple-Tutorial-for-Absolute. I succeeded to get a simple test to work.
Below here is the code I put together taken from these resources I just mentioned. So to clarify, the code I post IS WORKING. I just think it's not compact to be useful. I have an application in progress that already has all it Properties linked to TextBoxes,Labels,Buttons,DataGrids, ... but the Events if you click on a button aren't yet. Therefore the code below. The reason why I post my code is as follow:
Can the code for Private _oShowMsgBox As ICommand be shorter? From the looks of it I need 2 Subs or Functions to do the same what I could do in 1.
Private _oShowMsgBox As ICommand = New RelayCommand(New Action(Of Object)(AddressOf ShowMsgBoxSub),
New Predicate(Of Object)(Function() If(TextboxText = "", False, True)))
Is it necessary to have all those Properties in your file (properties for the Commands and binding data to the controls)? The application I'm working on has over 150 controls (TextBox,Label,Button,DataGrid) so the code is getting big pretty fast and looks like it's very inefficient.
Below the line is a snapshot of working code in my project. See it as a tiny part of what I have in total right now.
My xaml:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MVVM_Test" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="MainWindow" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:ViewModel x:Key="ViewModelDataSource" d:IsDataSource="True" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource ViewModelDataSource}}">
<Button Content="{Binding TextboxText}" Command="{Binding ShowMsgBox}"/>
<TextBox Text="{Binding TextboxText, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
For the first time I also succeeded to have NO code in my code behind file.
This is the RelayCommand I came up with:
Public Class RelayCommand
Implements ICommand
#Region "Fields"
Private ReadOnly _execute As Action(Of Object)
Private ReadOnly _canExecute As Predicate(Of Object)
#End Region
#Region "Constructors"
Public Sub New(ByVal execute As Action(Of Object))
Me.New(execute, Nothing)
End Sub
Public Sub New(ByVal execute As Action(Of Object), ByVal canExecute As Predicate(Of Object))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
_execute = execute
_canExecute = canExecute
End Sub
#End Region
#Region "ICommand Members"
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Return If(_canExecute Is Nothing, True, _canExecute(parameter))
End Function
Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
AddHandler CommandManager.RequerySuggested, value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
RemoveHandler CommandManager.RequerySuggested, value
End RemoveHandler
RaiseEvent(ByVal sender As System.Object, ByVal e As System.EventArgs)
End RaiseEvent
End Event
Public Sub Execute(parameter As Object) Implements ICommand.Execute
_execute(parameter)
End Sub
#End Region
End Class
Then in my ViewModel I have this:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Class ViewModel
Implements INotifyPropertyChanged
Private _sText As String
Public Property TextboxText As String
Get
Return _sText
End Get
Set(ByVal value As String)
_sText = value
RaisePropertyChanged()
End Set
End Property
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Protected Sub RaisePropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private _oShowMsgBox As ICommand = New RelayCommand(New Action(Of Object)(AddressOf ShowMsgBoxSub), New Predicate(Of Object)(Function() If(TextboxText = "", False, True)))
Public Property ShowMsgBox As ICommand
Get
Return _oShowMsgBox
End Get
Set(ByVal value As ICommand)
_oShowMsgBox = value
End Set
End Property
Public Sub ShowMsgBoxSub()
MessageBox.Show(TextboxText)
End Sub
End Class
For commands, which I suspect in your case are readonly, you should be able to use VB.Net's auto property feature:
Public Property ShowMsgBox _
As New RelayCommand( _
New Action(Of Object)(AddressOf ShowMsgBoxSub), _
New Predicate(Of Object)(Function() If(TextboxText = "", False, True)))
For bindable properties that change you will need to use the verbose property syntax and signal that the property has changed in the setter by firing the PropertyChanged event on your view model class that implements INotifyPropertyChanged.

Update UI when property is changed

I am quite new to WPF and I am confused about how Data Bindings should behave.
I have created a class that has 1 property ("status") and three methods that are responsible for changing the status. This class implements the INotifyPropertyChanged interface so that I am able to notify the calling code when the Status changes.
The class looks like this:
Public Class StreetLight
Implements System.ComponentModel.INotifyPropertyChanged
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private _status As String
Public Property Status As String
Get
Return _status
End Get
Set(ByVal value As String)
_status = value
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Satus"))
End Set
End Property
Public Sub New()
_status = "unknown"
End Sub
Public Sub Red()
Status = "Red"
End Sub
Public Sub Yellow()
Status = "Yellow"
End Sub
Public Sub Green()
Status = "Green"
End Sub
End Class
I have created a WPF User Control to represent this class.
This User Control is bound to an instance the StreetLight class. It displays the status of the StreetLight and allows the user to change the status using buttons:
<UserControl x:Class="StreetLightUC"
x:Name="StreetLightUC"
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:twpf="clr-namespace:TryingWPF"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="360">
<UserControl.Resources>
<twpf:StreetLight x:Key="theLight" PropertyChanged="theLight_PropertyChanged" />
</UserControl.Resources>
<StackPanel x:Name="StreetLightContent" Orientation="Horizontal">
<Label Width="100" HorizontalAlignment="Left">Street Light _Status</Label>
<Label x:Name="streetLightValue" Width="120" HorizontalAlignment="Right" Content="{Binding Path=Status, Mode=OneWay}"></Label>
<Button x:Name="Red" Click="TurnRed" Width="60">Turn Red</Button>
<Button x:Name="Green" Click="TurnGreen" Width="60">Turn Green</Button>
</StackPanel>
</UserControl>
My problem is that even when the status is changed for theLight, it is not updated in the Label that is bound to the Status property unless I create a new StreetLight and set the DataContext to this new instance in the "StreetLight_PropertyChanged" event that handles the PropertyChagned event for the "theLight"
Like so:
Public Class StreetLightUC
Public Sub New()
InitializeComponent()
End Sub
Private Sub TurnRed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim light As StreetLight= CType(FindResource("theLight"), StreetLight)
light.Red()
End Sub
Private Sub TurnGreen(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim light As StreetLight = CType(FindResource("theLight"), StreetLight)
light.Unlock()
End Sub
Private Sub theLight_PropertyChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)
Dim light As StreetLight = CType(sender, StreetLight )
Dim newLight As New StreetLight
newLight.Status = light.Status
StreetLightContent.DataContext = newLight
End Sub
End Class
Am I doing something wrong?
It doesn't seem like I should have to create a new instance of the class to display the updated status-property when this property is change....
Thanks,
-Frinny
You have a typo ("Satus" instead of "Status"):
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Satus"))
Should be:
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Status"))
With this typo, the binding doesn't see that "Status" has changed, and never updates. If you correct this, the PropertyChanged event will correctly reflect that "Status" has changed.

Attaching a custom DependencyProperty to a StackPanel in WPF?

I'm trying to make a mouse over for a stack panel in WPF using a custom DependencyProperty (StackPanels do not handle the MouseEnter event).
I've created a class for the DependencyProperty like so:
Public Class MouseEnterBehavior
Public Shared Property MouseEnterProperty As DependencyProperty =
DependencyProperty.RegisterAttached("MouseEnter",
GetType(ICommand),
GetType(MouseEnterBehavior),
New PropertyMetadata(Nothing, AddressOf MouseEnterChanged))
Public Shared Function GetMouseEnter(ByVal obj As DependencyObject) As ICommand
Return CType(obj.GetValue(MouseEnterProperty), ICommand)
End Function
Public Shared Sub SetMouseEnter(ByVal obj As DependencyObject, ByVal value As ICommand)
obj.SetValue(MouseEnterProperty, value)
End Sub
Public Shared Sub MouseEnterChanged(ByVal obj As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim element As UIElement = TryCast(obj, UIElement)
If element IsNot Nothing Then
AddHandler element.MouseEnter, AddressOf uiElement_MouseEnter
End If
End Sub
Public Shared Sub uiElement_MouseEnter(ByVal sender As Object, ByVal e As EventArgs)
Dim uiElement As UIElement = TryCast(sender, UIElement)
Dim command As ICommand = GetMouseEnter(uiElement)
If command IsNot Nothing And command.CanExecute(uiElement) Then
command.Execute(uiElement)
End If
End Sub
End Class
My View looks like this:
<Window x:Class="MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Height="76" vm:MouseEnterBehavior.MouseEnterCommand="{Binding MouseEnteredCommand}" HorizontalAlignment="Left" Margin="212,117,0,0" VerticalAlignment="Top" Width="88" Background="#72000000" />
</Grid>
</Window>
And my ViewModel looks like this:
Public Class MainWindowViewModel
Inherits ViewModelBase
Implements INotifyPropertyChanged
Private cmdMouseCommand As RelayCommand
Public ReadOnly Property MouseEnteredCommand As ICommand
Get
If cmdMouseCommand Is Nothing Then
cmdMouseCommand = New RelayCommand(AddressOf OnMouseEnterCommand)
End If
Return cmdMouseCommand
End Get
End Property
Private Sub OnMouseEnterCommand(ByVal obj As Object)
''//Do something
End Sub
End Class
Update
I was able to get the code to compile and run, however the binding doesn't occur. I can't seem to figure out why.
You've registered the dependency property as MouseEnteredCommand, but try to bind to MouseEnterCommand.
On a side note, the binding won't set your DependencyProperty using the Set call you've provided; it will call SetValue directly. You'll need to pass a callback into the RegisterAttached so that you'll be notified.
I do believe that this is your problem:
DependencyProperty.RegisterAttached("MouseEnteredCommand",
GetType(ICommand),
GetType(MainWindowViewModel)
the first GetType should be the type of the property ( you are OK here )
the second GetType shuold be the type of the containing class, in your case "MouseEnterBehavior"

Resources