WPF vb.net KeyBinding Delete

WPF vb.net KeyBinding Delete

budinsky
Contributor Contributor
2,605 Views
12 Replies
Message 1 of 13

WPF vb.net KeyBinding Delete

budinsky
Contributor
Contributor

I'm trying to bind the Delete key to my ListView in WPF.

I want to delete items from this listview using the delete key.

Do I have the KeyBinding in the correct location? Maybe I need to activate something, I don't know.

XAML for MainWindow:

<ListView PreviewMouseLeftButtonDown="MouseLBDown" PreviewMouseMove="OnMouseBMove" Drop="MouseDrop" DragEnter="MouseDragEnter" Name="lvScrsPre" Height="145" Width="260" AllowDrop="True">
    <ListView.InputBindings>
        <KeyBinding Key="Delete" Command="{Binding DeleteCommand}" />
    </ListView.InputBindings>
    <ListView.View>
        <GridView>
            <GridViewColumn Width="250">
                <GridViewColumn.Header>
                    <GridViewColumnHeader>Before Print</GridViewColumnHeader>
                </GridViewColumn.Header>
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Source="{Binding scrImg}" Margin="0,0,5,0" />
                            <TextBlock Text="{Binding scrList}" />
                        </StackPanel>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

Code-behind Custom class:

Public Class Scripts
    Public Property scrImg As String
    Public Property scrList As String
End Class

I thought this would work, but I'm just confused at this point. I think I'm doing a different approach to xml here:

Private Sub lvScrsPre_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
    If e.Key = Key.Delete Then

        If lvScrsPre.SelectedItem IsNot Nothing Then
            Me.lvScrsPre.Items.Remove(lvScrsPre.SelectedItem)
        End If
    End If
End Sub
0 Likes
2,606 Views
12 Replies
Replies (12)
Message 2 of 13

norman.yuan
Mentor
Mentor

Well, your question is really not related to AutoCAD .NET API programming at all, and I bet many, if not most, AutoCAD .NET programming still use Win Forms more than WPF in AutoCAD add-in as UI, thus you have much less chance of getting good replies for this kind of question in this forum than in more generic, WPF discussion forums, such as in Stackoverflow.com.

 

Having said that, let me give it a try: you may want to apply KeyBinding in the root UIElement, instead of ListView. If your UI is a dialog window, the root UIElement is <Window />, if it is a Palette in a custom PalatteSet, it is <UserControl />, so it would be something like:

 

<Window[UserControl] .....>

    <Window[UserControl].InputBindings>

        <KeyBinding Key="Delete" Command="{Binding DeleteCommand}" />

    </Window[UserControl].InputBindings>

</Window[UserControl]>

 

Of course, the DeleteCommand would have its "CanExecute" function properly coded so that if no item in the ListView is selected, the function returns False, so that the command is disabled, and pressing "Delete" button would not run the command's code.

 

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 3 of 13

budinsky
Contributor
Contributor

Thanks Norman, I agree that it's not directly AutoCAD and appreciate your advice about WPF. I will add a post to Stackoverflow and see what comes out of it.

I took your advice and changed the xaml to Window. Now I have a "CanExecute" function that pops up a messagebox as a test but don't know what the next step is to delete the current selected item in the ListView.

 

0 Likes
Message 4 of 13

norman.yuan
Mentor
Mentor

OK, I assume that the KeyBinding now does work, right?

 

ICommand, which is bound to the "Delete" key, must have an implemented CanExecute function that returns a boolean value. So, it is up to you to place code logic in it to decide what to do so that the Command either can be executed, or disabled. In your case, the logic in CanExecute() would be something like: check if the target listview has one or more items selected or not; if no, returns False, so the command is disabled (thus, pressing "Delete" would do nothing to the ListView); if yes, as optional, you may want to pop up a message box with "yes/No" button for user confirmation, then return True or False accordingly.

 

If you do use MVVM pattern, your ViewModel would have an ObservableCollection<Something> bound to the ListView and a SelectedSomething property that is bound to the ListView's SelectedItem. Then you only need one line of code as CanExecute function: (o)=>{ return SelectionSomething != null;}

 

Obviously, if you do not use MVVM pattern, you can still write code behind the view, as you do with Win Form (but a lot more code and messier). I'd not use WPF UI if MVVM pattern is not used.

 

 

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 5 of 13

budinsky
Contributor
Contributor

Yes, the Delete key now works, the next step is removing the ListView item/s.

 

I think I'm using the MVVM pattern. Still on a massive learning curve so I could be wrong.

I was thinking of using this for the SelectedItem:

<ListView Name="lvScrsPre" SelectedItem="{Binding Path=SelectedRule}

and maybe:

Inherits ObservableCollection(Of Scripts)

Is that anywhere near the right track?

0 Likes
Message 6 of 13

budinsky
Contributor
Contributor

I do have and ICommand.CanExecute

Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute

I actually have 3 ListView's going on. One is populated from files in a folder and the other 2 get drag and dropped from the first. I have no reference to an ObservableCollection

Dim items As List(Of Scripts) = New List(Of Scripts)()
...
lvScrsList.ItemsSource = items
0 Likes
Message 7 of 13

budinsky
Contributor
Contributor

Does this have something to do with the observable collection?

    Public Sub New()
        InitializeComponent()
        DataContext = New MyDataContext()
    End Sub

Really confused now.

0 Likes
Message 8 of 13

norman.yuan
Mentor
Mentor

The items that are bound to the listview should be ObservableCollection<Something>.

 

The ViewModel (MyDataContext class, according to your next post) should look like:

 

Public Class MyDataContext

  Inherit ViewModelBase  '' You probably have a viewmodelbase class that implementing INotifyPropertyChanged

  Private _selected As Something=Nothing

  Private _somthings As New ObservableCollection(of Something)()

  Private _deleteCommand As ICommand

  .....

  Public Sub New()

    '' Create commands,

    '' Initialize _somethings collection

  End Sub

  Public Property Somethings() As ObserverbaleCollection(of Something)

    Get 

      Return _somethings

    End Get

  End Property

  Public Property SelectedSomething() As Something

    Get

      Return _selected

    End Get

    Set (ByVal value As Something)

      _something = value

      OnPropertyChange("SelectedSomething") '' This method should be defined in ViewModelBase clase

  End Set

  ''  Other properties exposed for binding, such as commands

  ... ...

End Class

 

With this ViewModel (DataContext) to be bound to the UI, you can set the ListView's ItemsSource to be bound to "Somethings", and the ListView's SelectedItem to be bound to "SelectedSomething". And then finally, in the deleting command's CanExecute()  method, you simply test if property SelectedSomething is null or not.

 

Well, it is quite some learning to get started with WPF and MVVM pattern. Hopefully you can master the basics fast enough to use WPF UI in AutoCAD.

 

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 9 of 13

budinsky
Contributor
Contributor

I don't have MVVM implemented. I'm just using the xaml and code behind. ObservableCollection and INotifyPropertyChanged are not currently in my code.

Public Class DeleteCommand
    Implements ICommand

    Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
        Dim msg As String

        If parameter Is Nothing Then
            msg = "Button Clicked!"
        Else
            msg = parameter.ToString()
        End If

        MessageBox.Show(msg)
    End Sub

    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
        'check if the target ListView has one or more items selected. no returns false
        Return True
    End Function

    Public Event CanExecuteChanged As EventHandler
    Private Event ICommand_CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
End Class

Public Class MyDataContext
    Private _deleteCommand As ICommand = New DeleteCommand()

    Public ReadOnly Property DeleteCommand As ICommand
        Get
            Return _deleteCommand
        End Get
    End Property
End Class

I'll have sit do some more research before I can piece it all together and amend my code to suit.

0 Likes
Message 10 of 13

budinsky
Contributor
Contributor

After reading a bit and translating your code into what I think I needed.

I changed List to ObservableCollection

Dim items As ObservableCollection(Of Scripts) = New ObservableCollection(Of Scripts)()

Then I added the INotifyPropertyChanged

Public Class Scripts
    Implements INotifyPropertyChanged
    Private _scrlist As String
    Public Property scrList As String
        Get
            Return Me._scrlist
        End Get
        Set(value As String)
            If Me._scrlist <> value Then
                Me._scrlist = value
                Me.NotifyPropertyChanged("scrList")
            End If
        End Set
    End Property
    Public Event PropertyChanged As PropertyChangedEventHandler
    Private Event INotifyPropertyChanged_PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public Sub NotifyPropertyChanged(ByVal propName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
    End Sub
End Class

This is the grey area where I'm not sure what I'm doing. I gave it a shot.

Public Class MyDataContext
    Implements ICommand
    Private _selected As Scripts = Nothing
    Private _items As New ObservableCollection(Of Scripts)()
    Private _deleteCommand As ICommand
    '''''
    Public Sub New()
        '' Create commands,
        '' Initialize _items collection
    End Sub
    Public Property items() As ObservableCollection(Of Scripts)
        Get
            Return _items
        End Get
        Set(value As ObservableCollection(Of Scripts))
        End Set
    End Property
    Public Property _scrlist() As Scripts
        Get
            Return _selected
        End Get
        Set(ByVal value As Scripts)
            _items = value
            OnPropertyChange("_scrlist") '' This method should be defined in ViewModelBase class
        End Set
    End Property
    ''  Other properties exposed for binding, such as commands
    ''''''
    Public Sub Execute(parameter As Object) Implements ICommand.Execute
        _deleteCommand.Execute(parameter)
    End Sub
    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
        'check if the target ListView has one or more items selected. no returns false
        Return True
    End Function
    Public Event CanExecuteChanged As EventHandler
    Private Event ICommand_CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
End Class

I have an error about missing the OnPropertyChange not declared.

0 Likes
Message 11 of 13

norman.yuan
Mentor
Mentor

I am not sure why does your DataContext (MyDataContext class) implement ICommand. It should be a kind of ViewModel, which expose ICommand as part of its public properties, which can be bound to UI's controls, such as Buttons.

 

IMO, using WPF UI in AutoCAD add-ins only make sense when MVVM pattern is used, unless the add-in UI is extremely simple and only very small amount of code behind is enough to handle user interaction. In your case, you can only use code-behind without binding, if you choose so. Again, I'd not use WPF UI without implementing MVVM patter to take advantage of rich binding functionality.

 

Anyway, it may be too complicated and to off-topic to further the discussion in this forum. Here I attach an simple EXE WPF sample project to show how to use MVVM pattern to bind keys ("Insert" and "Delete" keys) to add/delete items from a WPF ListView. From the sample project you can see I only have one line of code that set the UI's DataContext, which can only be done in the XAML file, so that the entire functioning ListView adding/deleting process is done without UI code-behind at all. All logic is in ViewModel.

 

Hope this helps.

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes
Message 12 of 13

budinsky
Contributor
Contributor

Thanks for the example file, I tested it and it works for me. However, can you do a VB version? I tried to convert it myself and couldn't get the CanExecuteChanged to work. Also, using this method can you do a double click example as well?

0 Likes
Message 13 of 13

norman.yuan
Mentor
Mentor

Yeah, VB.NET does not have Envent "add"/"remove" accessor as in C#, even most online VB.NET<=>C# converters cannot convert that C# code to VB.NET correctly. You need to write more lines of code to do the same thing, which I had to search the Internet to find suitable code from others  (another reason to choose C# instead of VB.NET).

 

Anyway, here is the VB.NET equivalent code to the class file "WpfBaseLib.cs" in WpfBaseLib project:

 

Imports System
Imports System.ComponentModel
Imports System.Windows.Input
Imports System.Runtime.CompilerServices

Public Class RelayCommand
    Implements ICommand

    Public Property ExecuteDelegate() As Action(Of Object)
    Public Property CanExecuteDelegate() As Func(Of Object, Boolean)

    Public Sub New()
    End Sub

    Public Sub New(execute As Action(Of Object))
        Me.New(execute, Nothing)
    End Sub

    Public Sub New(execute As Action(Of Object), Optional canExecute As Func(Of Object, Boolean) = Nothing)
        If execute Is Nothing Then
            Throw New ArgumentNullException("execute")
        Else
            ExecuteDelegate = execute
        End If

        CanExecuteDelegate = canExecute

    End Sub

    Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
        Return If(CanExecuteDelegate Is Nothing, True, CanExecuteDelegate(parameter))
    End Function

    Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
        AddHandler(ByVal value As EventHandler)

            If CanExecuteDelegate IsNot Nothing Then
                AddHandler CommandManager.RequerySuggested, value
            End If

        End AddHandler
        RemoveHandler(ByVal value As EventHandler)
            If CanExecuteDelegate IsNot Nothing Then
                RemoveHandler CommandManager.RequerySuggested, value
            End If
        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
            CommandManager.InvalidateRequerySuggested()
        End RaiseEvent
    End Event

    Public Sub Execute(parameter As Object) Implements ICommand.Execute
        If ExecuteDelegate IsNot Nothing Then ExecuteDelegate.Invoke(parameter)
    End Sub

    Public Sub RaiseCanExecuteChanged()
        CommandManager.InvalidateRequerySuggested()
    End Sub

End Class

Public MustInherit Class ViewModelBase
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Protected Overridable Sub OnPropertyChanged(<CallerMemberName> ByVal Optional propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

End Class

So, you create a VB.NET class library, add references to PressentationCore.dll and PresentationFramework.dll. Then add new class file, and copy the above code into it. Hope this helps.

 

As for binding mouse click/double-click to command, if you search this online, you would find out that it is a bit more complicated. Maybe some code-behind would be simpler. But I would not let user to simply do "double-click" on a list view item to delete. I'd rather place a "remove" button beside each item, so user would know what to do, if he/she wants to delete item.

 

Norman Yuan

Drive CAD With Code

EESignature

0 Likes