在WPF应用程序中,我希望有一个用户跟踪系统来统计用户使用该应用程序的方式。换句话说,我正在寻找一种方法来跟踪正在执行的命令以及用户如何触发它们(通过单击工具栏按钮,使用键盘快捷键等)。到目前为止,我还没有找到一个很好的方法来使用WPF命令模式...
您是否有关于如何在不覆盖应用程序中使用的每个控件的情况下实现/设计此类内容的想法/建议?
出于讨论目的,我创建了一个非常基本的WPF应用程序,其中包含一个带有单个Save按钮,TextBox和ListBox的工具栏。我还添加了一个KeyBinding来按CTRL + S时触发Save命令。
第一个挑战是确定使用哪个设备(鼠标或键盘)来触发命令。
第二个挑战是确定用于触发命令的控件(命令源)是什么。当触发命令时,我不知道哪个控件有键盘焦点,我想知道用什么控件来触发命令(通常它是一个按钮,一个超链接,来自ContextMenu的MenuItem等)< / p>
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;
}
}
修改
我意识到我原来的问题有点模糊,我道歉。我会尝试提供更多信息并提出更准确的问题。
我知道存储已执行的命令列表非常简单。这里的挑战是检索最初用于触发命令的设备:鼠标还是键盘?
通过将跟踪逻辑放在“已执行”处理程序中,此时无法确定用户是否通过鼠标单击按钮,按下按钮上的Enter键或使用键盘来触发命令捷径。在我的示例中,可以通过单击工具栏按钮或按键盘上的CTRL + S来触发相同的命令。如何跟踪这些将触发相同命令的单独操作?
这可以在ViewModel层中实现吗?当我们到达命令处理程序时,已经太晚了:我们丢失了这些信息。我们真正了解所使用设备的唯一地方是View本身。如何将此信息传递给Command处理程序?唯一的方法是覆盖Button控件来拦截Click和KeyDown事件,以便为命令处理程序提供额外的上下文吗?
答案 0 :(得分:0)
如果使用MVVM模式,则命令将从视图绑定到View模型中的Command实例。您可以使用创建ICommand实现,该实现在执行事件时提供有关其自身的一些详细信息。也许使用命令提供程序/工厂/无论创建每个命令并将其连接到记录器/跟踪器。
答案 1 :(得分:0)
创建具有Stack<ICommand>
属性的Singleton或static
类,并将对此类的引用传递给Window
(或最好是视图模型)。您当然应该使用一些典型的Stack
和AddCommand
方法封装RemoveCommand
对象。然后,只要调用ICommand
,Push
就会调入Stack
。
但是,您需要在单独的类中定义ICommand
,或者最好使用在线找到的RelayCommand
形式。这是一个例子:
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;
}
我不确定你的第二个问题意味着什么,因为ICommand
被设置为相关控件的Command
属性的值,所以你应该已经知道什么是控件他们是,例如:
<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>