Connection Commands in AvalonEdit Used in ListView ItemTemplate Not Working

In my project, I used the AvalonEdit control. When I use keyboard shortcuts like Ctrl + C or Ctrl + V, the corresponding copy / paste commands work fine. I decided to use these commands in the context menu for greater usability, because some users get used to right-clicking instead of a shortcut. I used the following XAML code to control:

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

but when I run the program, these commands always appear disabled in the context menu as follows:

screenshot of context menu

When I first encountered this problem, I asked another question, but using MD.Unicorn (as you see in the comments below), I realized that when you place AvalonEdit in the ItemTemplate of the ListBox or ListView commands does not work.

Using MD.unicorn, I created the following test code to reproduce the result:

ViewModel class and simple class for data template

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

and XAML code for MainWindow

 <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> 

If you try this test, you will see that if the DataTemplate is used in the content control, its command binding in the context menu works fine, but they are disabled in the ListViewItem. Also note that the context menu in the DataTemplate works fine for the TextBox and shows that the ListView itself does not break the command chain.

How can I fix the context menu and connect to manage commands in listView items?

+4
source share
1 answer

This is what I used to overcome such problems - I hope it is useful for people (this general logic can be applied to a wide range of problems associated with the Avalon editor) ...

Actually, this is probably Avalon Avalon (in combination with ListItem , etc.). This will ruin the mouse manipulation, and I guess the focus (which should be in TextArea for commands and CanExecute for work.

The problem with mouse handling is that it’s as if you just press the windows context menu key, it displays a regular menu with commands enabled. The Avalon editor has complex mouse / key manipulation (it’s hard to make a good editor) - and on the keyboard it explicitly expresses “ focus ” on TextArea. You can also see the problem by setting a breakpoint on the CanCutOrCopy method ( Editing/EditingCommandHandler.cs , download Avalon source) that actually handles ApplicationCommands.Copy . For the keyboard menu, it first enters there, then pops up. For the mouse, it appears, and then exit, it checks CanExecute (included in this method). This is all wrong!

And the mistakes ...

There are no problems with your own teams, just set your teams normally and everything should work.

For ApplicationCommands (i.e. RoutedCommand ) it connects incorrectly - and Execute , CanExecute do not go where they need to, i.e. TextArea . To fix this, you need to rewire commands in your own wrappers - and basically invoke TextArea processing - these are just a few lines of code, but this is a necessary step (I don’t think there is a more “beautiful” solution to this, except for fixing the code Avalon - what could be pain, never crossed my mind).

(everything is based on your example - fill in the blanks where I forgot) Your 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> 

Code Behind - View Model:
(note: I left the name as you put it, but please do not use small caps for props :)

 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" }); } } } 

(note: just connect Window.DataContext to the view model, just like you)

And this requires two custom classes.

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

Notes:

EditText is just a dependency property - being able to bind Text (your text ) is Avalon's flaw. Here is just for fun, but you may need it, so I left it.

Use AvalonRelayCommand to redirect redirected application commands - for other things, use your own implementation of Command. These two classes are the core.

You need to use AvalonTextEditor instead of TextEditor - it's just a tiny shell - connect ContextMenu to TextEditor (apart from other problems, the menu items suffering due to the lack of a visual tree - and you cannot easily get any controls from it). And we need to return the TextEditor link from CommandParameter (which is set as ContextMenu ). This could only be done with some binding properties (without overriding TextEditor), but it seems cleaner this way.

On the XAML side - just a few small changes - use the shell editor - and you have a MenuItem style that injects correct parameter for each command (you can do it differently, it was better)

This is not a hack - we simply reduce the drawback of mouse control - by manually invoking the TextArea command. This is pretty much it.

Enjoy it!

+4
source

All Articles