简体   繁体   中英

How to track WPF commands?

In a WPF application, I want to have a User Tracking System to keep statistics on the way users are using the application. In other words, I'm looking for a way to track what commands are being executed and how they have been triggered by the user (by clicking on the toolbar button, by using keyboard shortcuts, etc). So far, I haven't found a nice way to do this while using the WPF command pattern...

Do you have ideas/suggestions on how to achieve/design something like this without overriding every control used in the application?

For discussion purposes, I created a very basic WPF application containing a toolbar with a single Save button, a TextBox and a ListBox. I also added a KeyBinding to trigger the Save command when pressing CTRL+S.

The first challenge is to determine which device (mouse or keyboard) was used to trigger the command.

The second challenge is to determine what is the control used to trigger the command (the command source). I'm not interested to know which control had keyboard focus when the command was triggered, I would like to know what control was used to trigger the command (usually it's a button, an hyperlink, a MenuItem from a ContextMenu, etc.)

MainWindow.xaml

<Window x:Class="TrackingCommands.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" x:Name="Me" Height="480" Width="600">
    <Window.CommandBindings>
        <CommandBinding Command="Save" Executed="OnSaveCommandExecuted" CanExecute="OnSaveCommandCanExecute" />
    </Window.CommandBindings>
    <Window.InputBindings>
        <KeyBinding Command="Save" Gesture="CTRL+S"/>
    </Window.InputBindings>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <ToolBarTray Grid.Row="0">
            <ToolBar>
                <Button Command="Save" Content="Save"/>
            </ToolBar>
        </ToolBarTray>
        <TextBox Grid.Row="1" TextWrapping="Wrap" AcceptsReturn="True"/>
    </Grid>
</Window>

MainWindow.xaml.cs

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void OnSaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
    {
        e.Handled = true;
    }

    private void OnSaveCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = true;
        e.Handled = true;
    }
}

EDIT

I realized my original question was a bit vague, I apologize. I will try to give more information and ask a more precise question.

I know it is simple enough to store a list of commands that have been executed. The challenge here is to retrieve which device was used to trigger the command initially: mouse or keyboard?

By putting the tracking logic in the "executed" handler, there is no way at this point to determine if the user triggered the command by clicking a button with the mouse, by pressing Enter on the button or if he used a keyboard shortcut. In my example, the same command can be triggered by clicking the toolbar button or by pressing CTRL+S on keyboard. How can I track these separate actions that will all trigger the same command?

Can this be achieve in the ViewModel layer? When we reach the command handler, it's already too late: we have lost this information. The only place we really know the device used is in the View itself. How to pass this information to the Command handler? Is the only way to do this is to override the Button control to intercept Click and KeyDown events in order to provide additional context to the command handler?

If you use the MVVM pattern then the Command would be bound from the View to a Command instance in the View Model. You could use create an ICommand implementation that provided an event when it was executed with some details about itself. Maybe use a command provider/factory/whatever to create each command and wire it up to a logger/tracker.

Create a Singleton or static class that has a Stack<ICommand> property and pass a reference to this class to your Window s (or preferably view models). You should of course encapsulate the Stack object using some typical AddCommand and RemoveCommand methods. Then, whenever an ICommand is called, Push it into the Stack .

However, you'll either need to define your ICommand s in separate classes, or preferably use a form of the RelayCommand found online. Here's an example:

private ActionCommand deleteCommand = new ActionCommand(action => DeleteCommand(AudioTrack),
    canExecute => CanDelete(AudioTrack)); 

public override ICommand Delete
{
    get { return deleteCommand; }
}

private void DeleteCommand(AudioTrack audioTrack)
{
    // Do work then add to Stack in CommandManager
    CommandManager.AddCommand(deleteCommand);
}

private bool CanDelete(AudioTrack audioTrack)
{
    return audioTrack != null;
}

I'm not exactly sure what your second question means, because the ICommand s are set as the value to the Command property of the relevant control, so you should already know what controls they are, eg.:

<MenuItem Header="Delete track" Command="{Binding Delete}" 
    CommandParameter="{Binding Release.ThinDiscs.CurrentItem}">
    <MenuItem.Icon>
        <Image Source="pack://application:,,,/App;component/Images/Delete.png" />
    </MenuItem.Icon>
</MenuItem>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM