How to bind ApplicationCommands to a ViewModel?
Asked Answered
S

2

12

I have successfully used a few custom commands using MVVM-Light, but I want my application to respond to the standard ApplicationCommands, not just at a Window level, but at a detailed item level as well.

I have a TreeView that I want to be able to copy and paste nodes in. Each TreeViewItem has its own ViewModel, and they are displayed via HierarchicalDataTemplates in XAML as there are several different types. I have implemented methods to copy, paste, as well as CanCopy and CanPaste on my ViewModel classes. If appropriate, I could implement MVVM-Light RelayCommands pointing to these easily enough, but that doesn't seem right.

I would like to access the commands using a menu, Ctrl+C and Ctrl+V, or eventually a context menu. I also don't want to break copy/paste functionality for other elements in my UI, such as TextBoxes. It seems appropriate to use the built-in ApplicationCommands for this purpose. However, I am only seeing examples of these being handled in a UserControl code-behind. I don't have (or otherwise need) a UserControl, nor is that really following MVVM.

Is there a way I can bind ApplicationCommand.Copy and ApplicationCommand.Paste commands to my ViewModels, i.e., in the data templates?

Swop answered 3/10, 2011 at 23:4 Comment(0)
S
9

I have resolved this using Behaviors attached to the TreeView. The TreeViewItems or Templates to not seem to get the commands routed to them. Fortunately, the TreeView also has a SelectedItem property that can be used to get the ViewModel!

(Behaviors are conceptually similar to the solution in the link in @Natxo's answer, but it doesn't resolve everything.)

The Behavior class:

public class TreeViewClipboardBehavior : Behavior<TreeView>
{
    protected override void OnAttached()
    {
        base.OnAttached();

        CommandBinding CopyCommandBinding = new CommandBinding(
            ApplicationCommands.Copy,
            CopyCommandExecuted,
            CopyCommandCanExecute);
        AssociatedObject.CommandBindings.Add(CopyCommandBinding);

        CommandBinding CutCommandBinding = new CommandBinding(
            ApplicationCommands.Cut,
            CutCommandExecuted,
            CutCommandCanExecute);
        AssociatedObject.CommandBindings.Add(CutCommandBinding);

        CommandBinding PasteCommandBinding = new CommandBinding(
            ApplicationCommands.Paste,
            PasteCommandExecuted,
            PasteCommandCanExecute);
        AssociatedObject.CommandBindings.Add(PasteCommandBinding);
    }

    private void CopyCommandExecuted(object target, ExecutedRoutedEventArgs e)
    {
        NestingItemTreeViewModelBase item = AssociatedObject.SelectedItem as NestingItemTreeViewModelBase;
        if (item != null && item.CanCopyToClipboard)
        {
            item.CopyToClipboard();
            e.Handled = true;
        }
    }

    private void CopyCommandCanExecute(object target, CanExecuteRoutedEventArgs e)
    {
        NestingItemTreeViewModelBase item = AssociatedObject.SelectedItem as NestingItemTreeViewModelBase;
        if (item != null)
        {
            e.CanExecute = item.CanCopyToClipboard;
            e.Handled = true;
        }
    }

    private void CutCommandExecuted(object target, ExecutedRoutedEventArgs e)
    {
        NestingItemTreeViewModelBase item = AssociatedObject.SelectedItem as NestingItemTreeViewModelBase;
        if (item != null && item.CanCutToClipboard)
        {
            item.CutToClipboard();
            e.Handled = true;
        }
    }

    private void CutCommandCanExecute(object target, CanExecuteRoutedEventArgs e)
    {
        NestingItemTreeViewModelBase item = AssociatedObject.SelectedItem as NestingItemTreeViewModelBase;
        if (item != null)
        {
            e.CanExecute = item.CanCutToClipboard;
            e.Handled = true;
        }
    }


    private void PasteCommandExecuted(object target, ExecutedRoutedEventArgs e)
    {
        NestingItemTreeViewModelBase item = AssociatedObject.SelectedItem as NestingItemTreeViewModelBase;
        if (item != null && item.CanPasteFromClipboard)
        {
            item.PasteFromClipboard();
            e.Handled = true;
        }
    }

    private void PasteCommandCanExecute(object target, CanExecuteRoutedEventArgs e)
    {
        NestingItemTreeViewModelBase item = AssociatedObject.SelectedItem as NestingItemTreeViewModelBase;
        if (item != null)
        {
            e.CanExecute = item.CanPasteFromClipboard;
            e.Handled = true;
        }
    }
}

The XAML

<TreeView Grid.Row="2" ItemsSource="{Binding SystemTreeRoot}">
    <i:Interaction.Behaviors>
        <local:TreeViewClipboardBehavior/>
    </i:Interaction.Behaviors>
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:MyViewModel}" ItemsSource="{Binding Children}">
            <!-- Template content -->
        </HierarchicalDataTemplate>
</TreeView>
Swop answered 12/10, 2011 at 18:59 Comment(0)
P
3

I believe that you are looking for CommandBindings. I use something similar for some textboxes:

    <DataTemplate x:Key="textBoxTemplate" >
        <TextBox>
            <TextBox.CommandBindings>
                <CommandBinding Command="ApplicationCommand.Copy" 
                                Executed="CommandBinding_Executed"
                                CanExecute="CommandBinding_CanExecute">
                </CommandBinding>
            </TextBox.CommandBindings>
        </TextBox>
    </DataTemplate>

Note that PreviewCanExecute and PreviewExecuted are also available.

Edit: Check out the sample here to make it MVVM compliant.

Presentation answered 4/10, 2011 at 9:58 Comment(3)
I had attempted this method, except I could not quite figure out where in my template I should include them. I was trying to get to the TreeViewItem, which is not in the XAML, and could not figure out proper syntax. I have a StackPanel at the root of my data template, so that seems a sensible location. However, this method requires a handler in the View code-behind, and does not bind to my ViewModels, which are the DataContext of the template. In any case, this is a viable solution path, even if it is not pure MVVM. Thanks!Swop
Mmmh, you're right. I've edited to show a good way to avoid VM related coding in the View. With this, i guess tou only need to know how to use in your template.Presentation
Frankly, I don't see that the referenced article really adds much. Beside the fact that it calls RegisterClassCommandBinding() for no apparent reason (adding the binding to the UI element's CommandBindings collection is sufficient), it really does nothing to change where the command binding is exposed to WPF (i.e. in the UI element) and where the command itself is implemented (i.e. in the model). The code shown in the answer above does as much just fine, but without all the ceremony.Brockman

© 2022 - 2024 — McMap. All rights reserved.