WPF Command Class Override CanExecuteChanged-Event? - wpf

At the moment I have the following Command class:
Public Class SubscribeCommand
Implements ICommand
Private ReadOnly _vm As MainWindowViewModel
Public Sub New(ByVal vm As MainWindowViewModel)
_vm = vm
End Sub
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
Return Not String.IsNullOrEmpty(_vm.Symbol)
End Function
Public Event CanExecuteChanged(ByVal sender As Object, ByVal e As System.EventArgs) Implements System.Windows.Input.ICommand.CanExecuteChanged
Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
_vm.Subscribe()
End Sub
End Class
In a tutorial i read, you have to implement add {} and remove {} for the canExecuteChanged-Event. But how can i do that, with vb.net?
thanks a lot..

I don't think you're required to implement the add and remove pieces for the CanExecuteChanged event. I'm pretty sure it'll work just fine the way you have it now. But if you did want to for some reason (to make it match what you see in this post in C#, for instance), you would change
Public Event CanExecuteChanged(ByVal sender As Object, ByVal e As System.EventArgs)
to
Public Custom Event CanExecuteChanged As EventHandler
AddHandler(ByVal value As EventHandler)
CommandManager.RequestSuggested += value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
CommandManager.RequestSuggested -= value
End RemoveHandler
End Event

if you wanna use Commands in wpf please look at the wpf frameworks out there. you will find at least two nice command implementation:
RelayCommand
DelegateCommand

Related

ICommand CanExecuteChanged not updating

I am trying for MVVM pattern basic level and got struck at ICommand CanExecute changed. I have XAML binding as follows:
<ListBox ItemsSource="{Binding Contact.Addresses}" x:Name="AddressCollections" Height="152" SelectedValue="{Binding SelectedAddress}"
DockPanel.Dock="Top" />
<Button Content="Add" Command="{Binding AddAddressCommand}" DockPanel.Dock="Top" />
<Button Content="Remove" Command="{Binding DeleteAddressCommand}" DockPanel.Dock="Bottom" />
Commands:
Public Class DeleteCommand
Implements ICommand
Private method As Object
Private methodname As String
Public Sub New(ByVal Controlname As String, ByVal mee As Object)
methodname = Controlname
method = mee
End Sub
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Select Case methodname
Case "Address"
Return TryCast(method, ModelView.Contacts.ContactMV).CanDeleteAddress()
Case "Numbers"
Return TryCast(method, ModelView.Contacts.ContactMV).CanDeleteNumbers
Case Else : Return False
End Select
End Function
Public Event CanExecuteChanged(sender As Object, e As EventArgs) Implements ICommand.CanExecuteChanged
Public Sub Execute(parameter As Object) Implements ICommand.Execute
Select Case methodname
Case "Address"
TryCast(method, ModelView.Contacts.ContactMV).DeleteAddress()
Case "Numbers"
TryCast(method, ModelView.Contacts.ContactMV).DeleteNumbers()
Case Else
End Select
End Sub
End Class
My ModelView:
Public Class ContactMV
Property Contact As Model.Contacts.ContactMod
Property AddAddressCommand As New Commands.AddCommand("Address", Me)
Property DeleteAddressCommand As New Commands.DeleteCommand("Address", Me)
Property SelectedAddress As Model.Contacts.AddressModel
Public Sub AddAddress()
If Contact.Addresses.Count = 0 Then
Contact.Addresses.Add(New Model.Contacts.AddressModel(Contact.Primary.ID, True))
Else
Contact.Addresses.Add(New Model.Contacts.AddressModel(Contact.Primary.ID, False))
End If
End Sub
Public Sub DeleteAddress()
If IsNothing(SelectedAddress) = False Then
Try
Contact.Addresses.Remove(SelectedAddress)
Catch ex As Exception
MsgBox("Address not found")
End Try
End If
End Sub
Public Function CanDeleteAddress()
If IsNothing(SelectedAddress) Then
Return False
Else
Return Contact.Addresses.Contains(SelectedAddress)
End If
End Function
End Class
The problem is that the Canexecutechanged is firing only at start, I actually want to get the delete button enabled only when something in the listbox is selected, and I want to get it done by MVVM - ICommand binding method. Could you please explain where i went wrong or miss understood the ICommand implementation.
Thank you.
Updated Relay iCommand code I use:
Public Class RelayCommand
Implements ICommand
''' <summary>
''' A command whose sole purpose is to relay its functionality to other objects by invoking delegates. The default return value for the CanExecute method is 'true'.
''' </summary>
''' <remarks></remarks>
#Region "Declarations"
Private ReadOnly _CanExecute As Func(Of Boolean)
Private ReadOnly _Execute As Action
#End Region
#Region "Constructors"
Public Sub New(ByVal execute As Action)
Me.New(execute, Nothing)
End Sub
Public Sub New(ByVal execute As Action, ByVal canExecute As Func(Of Boolean))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
_Execute = execute
_CanExecute = canExecute
End Sub
#End Region
#Region "ICommand"
Public Custom Event CanExecuteChanged As EventHandler Implements System.Windows.Input.ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
If _CanExecute IsNot Nothing Then
AddHandler CommandManager.RequerySuggested, value
End If
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If _CanExecute IsNot Nothing Then
RemoveHandler CommandManager.RequerySuggested, value
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
'This is the RaiseEvent block
'CommandManager.InvalidateRequerySuggested()
End RaiseEvent
End Event
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements System.Windows.Input.ICommand.CanExecute
If _CanExecute Is Nothing Then
Return True
Else
Return _CanExecute.Invoke
End If
End Function
Public Sub Execute(ByVal parameter As Object) Implements System.Windows.Input.ICommand.Execute
_Execute.Invoke()
End Sub
#End Region
End Class
Most of the code is a copy, but I understood the working by below comments.
As Raul OtaƱo has pointed out, you can raise the CanExecuteChanged. However, not all MVVM frameworks provide a RaiseCanExecuteChanged method. It's also worth noting that the actual event CanExecuteChanged must be called on the UI thread. So, if you're expecting a callback from some thread in your model, you need to invoke it back to the UI thread, like this:
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
Application.Current.Dispatcher.Invoke((Action)(() => { CanExecuteChanged(this, EventArgs.Empty); }));
}
}
I would very much recommend against calling CommandManager.InvalidateRequerySuggested() because although this works functionally, and is ok for small applications, it is indiscriminate and will potentially re-query every command! In a large system with lots of commands, this can be very very slow!
You must have in your ICommand implementation some method like RaiseCanExecuteChanged that fires the event CanExecuteChanged. Then every time that the selected item in the list changed, in your view model you execute the RaiseCanExecuteChanged from the command you want. Any way I suggest you to use any generic command, like the RelayCommand of GalaSoft MVVMLite library, or any implementation of DelegateCommand.
Hope this helps...

MEF and PlugIn properties updates

I'm new to MEF and started a project to test it. What I'm trying to do is opening a MainForm that would load plugins based on an Interface. Those plugins need to be able to exchange information between them and the MainForm should be able to communicate with all of them too. So I started by creating my MainForm that loads a plugin. The plugin is only a form containing a ListBox. On the MainForm I have a button. I want that button to send a List(of String) to the plugin and that plugin to load that List(of String) in the ListBox. Currently, when I click on the MainForm button, it sends the list to the plugin. But the list is not loading in the plugin ListBox. Looking to find the problem, I added a new button on the MainForm to verify that the plugin property actually contains the list(of string) I sent it. And yes, the list contains all my strings. The problem needs to be that the ListBox isn't refreshing?
Part of the interface:
Public Interface IPlugIn
Property PlugInName as string
Property Files As List(Of String)
End Interface
Code in the MainForm Button:
Dim currentPlugIn As Contract.API.IPlugIn
currentPlugIn = PlugIns.Find(Function(x) x.PlugInName = "Test")
currentPlugIn.Files = IO.Directory.GetFiles("SomeFolder").ToList
Code in the PlugIn:
<Export(GetType(Contract.API.IPlugIn))> _
Public Class UserControl1
Implements System.ComponentModel.INotifyPropertyChanged, Contract.API.IPlugIn
Public Property Files As System.Collections.Generic.List(Of String) Implements
Contract.API.IPlugIn.Files
Get
If IsNothing(_files) Then
_files = New List(Of String)
End If
Return _files
End Get
Set(value As System.Collections.Generic.List(Of String))
_files = value
OnPropertyChanged("Files")
End Set
End Property
Public Event PropertyChanged(sender As Object, e As
System.ComponentModel.PropertyChangedEventArgs) Implements
System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New
ComponentModel.PropertyChangedEventArgs(propertyName))
End Sub
Code in PlugIn XAML:
<ListBox Name="lstFiles" ItemsSource="{Binding Path=Files}"/>
What's the problem? I searched the Internet for examples and found hundreds, but none of them is showing how to do what I want to do. Just before posting my question here, I added the INotifyPropertyChanged, it didn't resolved the problem. Would it be better for me to use PRISM, Caliburn.Micro or is MEF only going to be okay?
Thanks for your help!
Thanks everyone!
I finally found the answer.
I implemented PRISM EventAggregator and did change the following
In the Interface and PlugIns
Completely removed
Public ReadOnly Property PlugInUI As System.Windows.Controls.UserControl Implements
Contract.API.IPlugIn.PlugInUI
Get
Dim myUI As New UserControl1
Return myUI
End Get
End Property
In the Host
Changed
Public Sub OnImportsSatisfied() Implements
System.ComponentModel.Composition.IPartImportsSatisfiedNotification.OnImportsSatisfied
For Each plugInItem In WidgetList
Desktop.Children.Add(plugInItem.PlugInView)
Next
End Sub
to
Public Sub OnImportsSatisfied() Implements
System.ComponentModel.Composition.IPartImportsSatisfiedNotification.OnImportsSatisfied
For Each plugInItem In WidgetList
Desktop.Children.Add(plugInItem)
Next
End Sub
And now everything works as I wanted!
Athari, thanks for your comment.
I did fix the properties to ObservableCollection, but didn't fix the problem. Seems I'm missing something else.
Here's the full code.
Interface
Namespace API
Public Interface IPlugIn
Property Files As System.Collections.ObjectModel.ObservableCollection(Of String)
ReadOnly Property PlugInUI As System.Windows.Controls.UserControl
End Interface
End Namespace
PlugIn
Imports System.ComponentModel.Composition
<Export(GetType(Contract.API.IPlugIn))> _
Public Class UserControl1
Implements Contract.API.IPlugIn, System.ComponentModel.INotifyPropertyChanged,
System.Collections.Specialized.INotifyCollectionChanged
Private _files As System.Collections.ObjectModel.ObservableCollection(Of String)
Public Property Files As System.Collections.ObjectModel.ObservableCollection(Of String)
Implements Contract.API.IPlugIn.Files
Get
If IsNothing(_files) Then
_files = New System.Collections.ObjectModel.ObservableCollection(Of String)
End If
Return _files
End Get
Set(value As System.Collections.ObjectModel.ObservableCollection(Of String))
_files = value
OnPropertyChanged("Files")
OnCollectionChanged(New
Collections.Specialized.NotifyCollectionChangedEventArgs _
(Specialized.NotifyCollectionChangedAction.Reset))
End Set
End Property
Public ReadOnly Property PlugInUI As System.Windows.Controls.UserControl Implements
Contract.API.IPlugIn.PlugInUI
Get
Dim myUI As New UserControl1
Return myUI
End Get
End Property
Public Event PropertyChanged(sender As Object, e As
System.ComponentModel.PropertyChangedEventArgs) Implements
System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(Optional propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New
ComponentModel.PropertyChangedEventArgs(propertyName))
End Sub
Public Event CollectionChanged(sender As Object, e As
System.Collections.Specialized.NotifyCollectionChangedEventArgs) Implements
System.Collections.Specialized.INotifyCollectionChanged.CollectionChanged
Public Sub OnCollectionChanged(args As
System.Collections.Specialized.NotifyCollectionChangedEventArgs)
RaiseEvent CollectionChanged(Me, args)
End Sub
End Class
PlugIn XAML
<Grid>
<ListBox Height="248" HorizontalAlignment="Left" Margin="30,31,0,0" Name="ListBox1"
VerticalAlignment="Top" Width="241" ItemsSource="{Binding Files}">
</Grid>
Host App
Imports System.ComponentModel.Composition
Imports System.ComponentModel.Composition.Hosting
Public Class HostApp
<ImportMany(GetType(Contract.API.IPlugIn))> _
Public Property PlugIns As List(Of Contract.API.IPlugIn)
Private _container As CompositionContainer
Public Sub Compose()
Dim catalog As New AggregateCatalog
catalog.Catalogs.Add(New DirectoryCatalog("pluginfolder"))
_container = New CompositionContainer(catalog)
_container.ComposeParts(Me)
End Sub
Public Sub LoadPlugIns()
Compose()
For Each item In PlugIns
Desktop.Children.Add(item.PlugInUI)
Next
End Sub
Private Sub HostApp_Loaded(sender As Object, e As System.Windows.RoutedEventArgs)
Handles Me.Loaded
LoadPlugIns()
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
Handles Button1.Click
Dim fileList As New Collections.ObjectModel.ObservableCollection(Of String)
For Each fileItem As String In IO.Directory.GetFiles("Somefolder")
fileList.Add(fileItem)
Next
PlugIns.Item(0).Files = fileList
End Sub
End Class
I need the Files property to be listed in the PlugIn ListBox.
Thanks again for your help!

VB.net WPF DataGrid ObservableCollection Binding property update

I am using VB.NET and WPF within Visual Studio 2010 Express.
Currently, I have:
A DataGrid by the name of downloadListDG. This has a column which is a template containing an image.
An ObservableCollection of a custom DownloadListItem class.
This DownloadListItem has a public property which is another custom class.
This class has a private dim which is a StateType (a custom enum), and a public readonly property which returns a string depending on what the StateType is (actually an image URI if you're curious).
The DownloadListItem also has a public property which just returns the StateType (this is just for binding purposes)
My problem is that whenever the StateType changes, the image column in the DataGrid does not change. I have been trying to use the IPropertyChangedNofity, but nothing changes, so either I'm using it incorrectly or I need to use another method.
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
AddHandler ControllerRef.StateChanged, AddressOf StateChangeHandler
Private Sub StateChangeHandler(NewState As State)
MsgBox(NewState)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CurrentState"))
End Sub
Thanks in advance
Make sure the PropertyChanged event is notifying the UI of the property name you are bound to, not the property that triggers the change. Example:
Imports System.ComponentModel
Public Class DownloadListItem : Implements INotifyPropertyChanged
Friend Enum StateEnum
State1 = 0
State2 = 1
End Enum
Private _CurrentState As StateEnum
Private Sub ChangeEnumValue(NewValue As StateEnum)
_CurrentState = NewValue
OnPropertyChanged("ImageURI")
End Sub
Public ReadOnly Property ImageURI As String
Get
' TODO: Implement conditional logic to return proper value based on CurrentState Enum
End Get
End Property
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(PropertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
End Sub
End Class

In Silverlight, add custom property to a generated Linq class + manage events

I'm using Linq to SQL classes in my WCF. Those classes are returned from the WCF methods to the Silverlight. Now, I want to add a custom property on a the generated class (Silverlight side) and trigger a PropertyChangedEvent on that particular property, based on another PropertyChangedEvent from another property. To be clear, here's a piece of code that doesn't work :
Partial Public Class DataConnection
Public Sub New()
AddHandler Me.PropertyChanged, AddressOf _PropertyChanged
End Sub
Private Sub _PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs)
If e.PropertyName = "ConnectionType" Then
Me.RaisePropertyChanged("ConnectionTypeEnum")
End If
End Sub
Private _ConnectionTypeEnum As String
Public ReadOnly Property ConnectionTypeEnum() As String
Get
Select Case Me.ConnectionType
Return //Something based on ConnectionType //
End Select
End Get
End Property
End Class
The problem is that the code in New() is never executed, so I never know when the ConnectionType is changed, so I can't trigger the PropertyChanged on ConnectionTypeEnum. (this property is used a in One-Way binding so I need it)
Does anyone have a solution for this ?
Thanks
You can use OnDeserializedAttribute
<OnDeserializedAttribute()> _
Public Sub WhenDeserialized(context As StreamingContext)
AddHandler Me.PropertyChanged, AddressOf _PropertyChanged
End Sub

Layered INotifyPropertyChanged

I have a class that implements INotifyPropertyChanged for a property.
I have a control that is bound to that property.
I have another class that listens to the propertychanged event. In the event handler of that class I change the value of the property in code.
The problem I have is that I don't want to do any logic in the event handler for the next time it will fire due to the change of the property due to code.
However if the user changes the value of the property in the mean time (via async gui input) I still want the logic to fire. I also need to make sure that the control gets updated (this is twoway binding).
What is the best way to do this without this becoming a complete mess?
One way to accomplish this would be to refactor the setter on your property so that it called a method taking a parameter indicating whether or not to raise the event. Here is a simple code sample:
Imports System.ComponentModel
Public Class Class1
Implements INotifyPropertyChanged
Public Property MyData() As String
Get
Return _myData
End Get
Set(ByVal value As String)
SetMyData(value, True)
End Set
End Property
Private Sub SetMyData(ByVal value As String, ByVal triggerPropertyChanged As Boolean)
_myData = value
If triggerPropertyChanged Then
OnPropertyChanged("MyData")
End If
End Sub
Private _myData As String
Private Sub OnPropertyChanged(ByVal propertyName As String)
SetMyData("new value", False)
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
Your question is a bit vague, but you can check to see if the value has actually changed to determine if you should do your logic.
It would be better if you include some code to be more specific in your question.

Resources