2013-02-16 54 views
4

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

<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的ItemTemplate中或ListView命令不起作用时。

随着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; 
    } 
} 

和主窗口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

我试过你的代码,它工作得很好!我使用了[This CodeProject article](http://www.codeproject。com/Articles/42490/Using-AvalonEdit-WPF-Text-Editor),并将代码放入xaml中。 – 2013-02-17 12:57:53

+0

问题可能来自您省略的'avalonedit:TextEditor'的其他属性。 – 2013-02-17 12:59:34

+0

@ MD.Unicorn:感谢您的评论。该文章使用的是Text属性的控件的内容属性来显示文本,但我使用依赖属性来使用绑定来显示控件内容,根据[这篇文章](http://stackoverflow.com/questions/14855304)/two-way-binding-in-avalonedit-doesnt-work),所以我必须缺少一些破坏命令绑定的东西。你有什么主意吗? – 2013-02-17 13:50:22

回答

4

这是我用来获取过去类似的问题 - 我希望它(这个一般的逻辑可应用于广泛的阿瓦隆编辑器相关的问题)有用的人......

实际发生的可能是阿瓦隆的过错(与ListItem等组合)。它打乱了鼠标操作和我猜测的焦点(这应该是在TextArea命令和CanExecute工作

mouse handling是问题 - 因为如果你只需按windows context menu键它会弹出一个使用启用命令的常规菜单 Avalon编辑器具有复杂的鼠标/键处理功能(它很难编辑为 ) - 并且在键盘上,它在TextArea上执行了明确的'focus'。您还可以通过将方法(Editing/EditingCommandHandler.cs,下载 Avalon源)上的断点,其中实际ly处理 ApplicationCommands.Copy。对于'键盘'菜单,它首先在 那里,然后弹出。对于“鼠标”,它弹出 - 然后在 退出它检查CanExecute(进入该方法)。那是全部 错!

而勘误表...

你自己的命令没有问题,只是正常暴露你的命令,一切都应该工作。

对于ApplicationCommands(即RoutedCommand)不连线正确 - 和ExecuteCanExecute不走它应该,即TextArea。为了解决这个问题,你需要将这些命令写入你自己的包装中 - 并且基本上调用TextArea处理 - 这只是几行代码,但这是必要的步骤(我认为没有更好的解决方案这不足以修复Avalon的代码 - 这可能是一种痛苦,但我从未想过)。

(全部是基于你的榜样 - 填写在我离开了空格) 您的XAML:

<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文本(你text) - 这是一个Avalon缺点。这里只是为了好玩,但你可能需要它,所以我把它留下了。

使用AvalonRelayCommand重新连接应用程序路由命令 - 对于其他的东西使用你自己的命令实现。这两类是核心。

您需要使用AvalonTextEditor而不是文本编辑 - 这只是一个很小的包装 - 挂钩ContextMenu与其他问题TextEditor(分开,菜单项suffering由于缺乏visual tree - 你不能得到任何控制从它很容易)。我们需要从CommandParameter(设置为ContextMenu)获得TextEditor的编号。这可以通过一些附件属性来完成(无需重写TextEditor),但是这种方式看起来更清晰。在XAML方面 - 只需进行一些小的修改 - 使用包装编辑器 - 并且您有一个MenuItem样式,其中injects是每个命令的正确参数(您可以通过其他方式更好地实现)。

这不是一个hack - 我们只是shortcutting的 鼠标操作的缺点 - 通过手动调用TextArea命令处理。 这就是它。

Enjoy!

+0

哇!我真的很享受!谢谢。 – 2013-04-07 07:05:35

+0

不客气,如果有帮助,很高兴 – NSGaga 2013-04-07 09:35:22