在ListView的ItemTemplate中使用的AvalonEdit中的挂钩命令不起作用

时间:2013-02-16 10:46:07

标签: c# .net wpf binding avalonedit

我在项目中使用了AvalonEdit控件。当我使用Ctrl + C或Ctrl + V等快捷键时,相关的复制/粘贴命令可以正常工作。我决定在上下文菜单中使用这些命令以获得更多可用性,因为有些用户习惯于右键单击而不是快捷键。我使用以下XAML代码进行控制:

<avalonedit:TextEditor.ContextMenu>
    <ContextMenu>
         <MenuItem Command="Undo" />
         <MenuItem Command="Redo" />
         <Separator/>
         <MenuItem Command="Cut" />
         <MenuItem Command="Copy" />
         <MenuItem Command="Paste" />
     </ContextMenu>
</avalonedit:TextEditor.ContextMenu>

但是当我运行程序时,这些命令总是在上下文菜单中显示为禁用,如下所示:

screenshot of context menu

当我第一次遇到这个问题时,我发布了一个不同的问题但是在MD.Unicorn的帮助下(正如你在下面的评论中看到的那样)我意识到当你将AvalonEdit放在ListBox或ListView命令的ItemTemplate中时不行。

在MD.unicorn的帮助下,我创建了以下测试代码来重现结果:

ViewModel类和数据模板的简单类

public class MyViewModel : INotifyPropertyChanged
{
    public MyViewModel()
    {
        collection = new ObservableCollection<myClass>();
        mc = new myClass();
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propName)
    {
        var h = PropertyChanged;
        if (h != null)
            h(this, new PropertyChangedEventArgs(propName));
    }

    public ObservableCollection<myClass> collection { get; set; }
    public myClass mc { get; set; }
}

public class myClass
{
    public string text { get; set; }
}

public partial class MainWindow : Window
{
    MyViewModel _viewModel = new MyViewModel();

    public MainWindow()
    {
        InitializeComponent();

        this.DataContext = _viewModel;
    }
 }

MainWindow的XAML代码

<Window.Resources>
    <DataTemplate DataType="{x:Type local:myClass}">
        <StackPanel>
        <avalonedit:TextEditor x:Name="xmlMessage" 
        SyntaxHighlighting="XML" ShowLineNumbers="True"  >
            <avalonedit:TextEditor.ContextMenu>
                <ContextMenu>
                    <MenuItem Command="Undo" />
                    <MenuItem Command="Redo" />
                    <Separator/>
                    <MenuItem Command="Cut" />
                    <MenuItem Command="Copy" />
                    <MenuItem Command="Paste" />
                </ContextMenu>
            </avalonedit:TextEditor.ContextMenu>
        </avalonedit:TextEditor>
        <TextBox Text="test" />
        </StackPanel>
    </DataTemplate>
</Window.Resources>
<DockPanel>
    <ListView ItemsSource="{Binding collection}" />
    <ContentControl Content="{Binding mc}" />
</DockPanel>

如果您尝试此测试,您可以看到如果在内容控件上使用DataTemplate,它在上下文菜单中的命令绑定工作正常,但在ListViewItem中它们被禁用。 另请注意,DataTemplate中的上下文菜单适用于TextBox,并显示ListView本身并不会破坏命令链。

如何修复上下文菜单并连接到listView项目中的控制命令?

1 个答案:

答案 0 :(得分:6)

这是我过去遇到的类似问题 - 我希望它对人们有用(这种一般逻辑可以应用于各种Avalon编辑器相关问题)...

实际发生的可能是Avalon的错(与ListItem等结合使用)。它弄乱了鼠标操作,我猜测焦点(应该在命令的TextAreaCanExecute上工作。

  

mouse handling就是问题 - 好像你只需按windows context menu键就会弹出一个带有启用命令的常规菜单。   Avalon编辑器具有复杂的鼠标/键处理功能(很难制作一个   好的编辑器 - 并且在键盘上它显示了一个明确的'focus'   文本区。您还可以通过在断点上设置断点来查看问题   CanCutOrCopy方法(Editing/EditingCommandHandler.cs,下载   实际处理的Avalon源)   ApplicationCommands.Copy。对于“键盘”菜单,它首先进入   然后弹出。对于'鼠标',它会弹出 - 然后再打开   退出它检查CanExecute(输入该方法)。那是所有   错了!

和勘误表......

您自己的命令没有问题,只是正常公开命令,一切都应该有效。

对于ApplicationCommands(即RoutedCommand),它无法正确连接 - ExecuteCanExecute不会到达应有的位置,即{ {1}}。要纠正你需要TextArea命令到你自己的包装器 - 并且基本上调用TextArea处理 - 这只是几行代码,但它是必要的一步(我不认为有更多的'美丽的'解决方案,没有修复Avalon代码 - 这可能是一个痛苦,从来没有超出我的想法)。

(一切都基于你的例子 - 填写我遗漏的空白) 你的XAML:

rewire

背后的代码 - 视图模型:
(注意:我按照你的说法离开了命名 - 但不要使用小型大写的道具:)

<Window.Resources>
    <DataTemplate DataType="{x:Type my:myClass}">
        <StackPanel>
            <my:AvalonTextEditor x:Name="xmlMessage" SyntaxHighlighting="XML" ShowLineNumbers="True" EditText="{Binding text}" >
                <my:AvalonTextEditor.ContextMenu>
                    <ContextMenu x:Name="mymenu1">
                        <ContextMenu.Resources>
                            <Style TargetType="MenuItem">
                                <Setter Property="CommandParameter" Value="{Binding Path=., RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
                            </Style>
                        </ContextMenu.Resources>
                        <MenuItem Header="My Copy" Command="{Binding CopyCommand}" />
                        <MenuItem Header="My Paste" Command="{Binding PasteCommand}" />
                        <MenuItem Header="My Cut" Command="{Binding CutCommand}" />
                        <MenuItem Header="My Undo" Command="{Binding UndoCommand}" />
                        <MenuItem Header="My Redo" Command="{Binding RedoCommand}" />
                        <Separator />
                        <MenuItem Command="Undo" />
                        <MenuItem Command="Redo" />
                        <Separator/>
                        <MenuItem Command="Cut" />
                        <MenuItem Command="Copy" />
                        <MenuItem Command="Paste" />
                    </ContextMenu>
                </my:AvalonTextEditor.ContextMenu>
            </my:AvalonTextEditor>
        </StackPanel>
    </DataTemplate>
</Window.Resources>
<StackPanel>
    <DockPanel>
        <ListView ItemsSource="{Binding collection}" />
        <ContentControl Content="{Binding mc}" />
    </DockPanel>
</StackPanel>

(注意:只需将public class MyViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public MyViewModel() { collection = new ObservableCollection<myClass>(new[] { new myClass{ text = "some more test - some more test - some more test - some more test - some more test - some more test - some more test - some more test - some more test - " }, new myClass{ text = "test me test me = test me test me = test me test me = test me test me = test me test me = test me test me = " }, new myClass{ text = "test again - test again - test again - test again - test again - " }, new myClass{ text = "test again - test again - " }, new myClass{ text = "test again - " }, new myClass{ text = "test" }, }); mc = new myClass(); } public ObservableCollection<myClass> collection { get; set; } public myClass mc { get; set; } } public class myClass { public string text { get; set; } AvalonRelayCommand _copyCommand; public AvalonRelayCommand CopyCommand { get { return _copyCommand ?? (_copyCommand = new AvalonRelayCommand(ApplicationCommands.Copy) { Text = "My Copy" }); } } AvalonRelayCommand _pasteCommand; public AvalonRelayCommand PasteCommand { get { return _pasteCommand ?? (_pasteCommand = new AvalonRelayCommand(ApplicationCommands.Paste) { Text = "My Paste" }); } } AvalonRelayCommand _cutCommand; public AvalonRelayCommand CutCommand { get { return _cutCommand ?? (_cutCommand = new AvalonRelayCommand(ApplicationCommands.Cut) { Text = "My Cut" }); } } AvalonRelayCommand _undoCommand; public AvalonRelayCommand UndoCommand { get { return _undoCommand ?? (_undoCommand = new AvalonRelayCommand(ApplicationCommands.Undo) { Text = "My Undo" }); } } AvalonRelayCommand _redoCommand; public AvalonRelayCommand RedoCommand { get { return _redoCommand ?? (_redoCommand = new AvalonRelayCommand(ApplicationCommands.Redo) { Text = "My Redo" }); } } } 连接到视图模型,就像你所做的那样)

这需要两个自定义类来包装。

Window.DataContext

注意:

public class AvalonTextEditor : TextEditor { #region EditText Dependency Property public static readonly DependencyProperty EditTextProperty = DependencyProperty.Register( "EditText", typeof(string), typeof(AvalonTextEditor), new UIPropertyMetadata(string.Empty, EditTextPropertyChanged)); private static void EditTextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { AvalonTextEditor editor = (AvalonTextEditor)sender; editor.Text = (string)e.NewValue; } public string EditText { get { return (string)GetValue(EditTextProperty); } set { SetValue(EditTextProperty, value); } } #endregion #region TextEditor Property public static TextEditor GetTextEditor(ContextMenu menu) { return (TextEditor)menu.GetValue(TextEditorProperty); } public static void SetTextEditor(ContextMenu menu, TextEditor value) { menu.SetValue(TextEditorProperty, value); } public static readonly DependencyProperty TextEditorProperty = DependencyProperty.RegisterAttached("TextEditor", typeof(TextEditor), typeof(AvalonTextEditor), new UIPropertyMetadata(null, OnTextEditorChanged)); static void OnTextEditorChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e) { ContextMenu menu = depObj as ContextMenu; if (menu == null || e.NewValue is DependencyObject == false) return; TextEditor editor = (TextEditor)e.NewValue; NameScope.SetNameScope(menu, NameScope.GetNameScope(editor)); } #endregion public AvalonTextEditor() { this.Loaded += new RoutedEventHandler(AvalonTextEditor_Loaded); } void AvalonTextEditor_Loaded(object sender, RoutedEventArgs e) { this.ContextMenu.SetValue(AvalonTextEditor.TextEditorProperty, this); } } public class AvalonRelayCommand : ICommand { readonly RoutedCommand _routedCommand; public string Text { get; set; } public AvalonRelayCommand(RoutedCommand routedCommand) { _routedCommand = routedCommand; } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public bool CanExecute(object parameter) { return _routedCommand.CanExecute(parameter, GetTextArea(GetEditor(parameter))); } public void Execute(object parameter) { _routedCommand.Execute(parameter, GetTextArea(GetEditor(parameter))); } private AvalonTextEditor GetEditor(object param) { var contextMenu = param as ContextMenu; if (contextMenu == null) return null; var editor = contextMenu.GetValue(AvalonTextEditor.TextEditorProperty) as AvalonTextEditor; return editor; } private static TextArea GetTextArea(AvalonTextEditor editor) { return editor == null ? null : editor.TextArea; } } 只是一个依赖属性 - 能够EditText一个文本(你的bind) - 这是Avalon的缺点。这里只是为了好玩,但你可能需要它,所以我把它留在了。

使用text重新连接应用程序路由命令 - 其他内容使用您自己的Command实现。这两个类是核心。

您需要使用AvalonRelayCommand代替TextEditor - 这只是一个小包装 - 将AvalonTextEditorContextMenu连接起来(除了其他问题,菜单项为{{1}来自TextEditor的缺失 - 你无法轻易获得任何控件。我们需要从suffering(设置为visual tree)获得TextEditor的参考号。这可以通过一些附件属性(没有覆盖TextEditor)完成,但这样看起来更干净。

在XAML方面 - 只是一些小改动 - 使用包装器编辑器 - 你有一个CommandParameter样式,ContextMenu每个命令的正确参数(你可以通过其他方式做到,这更好)。

  

这不是MenuItem - 我们只是缩短了它的缺点   鼠标处理 - 通过手动调用injects命令处理。   这就是它。

享受!