wpf ListView的ItemTemplate中使用的AvalonEdit中的连接命令不起作用

ffscu2ro  于 2023-10-22  发布在  其他
关注(0)|答案(2)|浏览(117)

我在我的项目中使用了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>

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

当我第一次遇到这个问题时,我发布了一个不同的问题,但在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;
    }
 }

主窗口的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项目中的控制命令?

qxgroojn

qxgroojn1#

这是我过去用来解决类似问题的方法-我希望它对人们有用(这个一般逻辑可以应用于各种Avalon编辑器相关问题)。
实际发生的可能是阿瓦隆的故障(与ListItem等组合)。它搞乱了鼠标处理,我猜重点(这应该是在TextArea的命令和CanExecute的工作。
mouse handling是问题所在-就好像你只需按windows context menu键,它就会弹出一个常规菜单,其中包含启用的命令。Avalon编辑器有一个复杂的鼠标/键处理(很难成为一个好的编辑器)-在键盘上,它在TextArea上显式地执行'focus'。您还可以通过在实际处理ApplicationCommands.CopyCanCutOrCopy方法(Editing/EditingCommandHandler.cs,下载Avalon源代码)上设置断点来查看问题。对于“键盘”菜单,它首先进入那里,然后弹出。对于“鼠标",它会弹出-然后在退出时检查CanExecute(进入该方法)。这都是错的!**
还有勘误表...
你自己的命令没有问题,只要正常地暴露你的命令,所有的命令都应该工作。
对于ApplicationCommands(即RoutedCommand)它没有正确布线-ExecuteCanExecute没有去它应该去的地方,即。TextArea。为了纠正这个问题,你需要将命令rewire到你自己的 Package 器中-基本上调用TextArea处理-这只是几行代码,但这是一个必要的步骤(我不认为有一个更“漂亮”的解决方案,除了修复Avalon代码-这可能是一个痛苦,从来没有想过)。
(all是基于你的例子-填写我遗漏的空白)你的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>

代码隐藏视图模型:
(note:我离开了命名,因为你把它-但不要使用小型大写的 prop :)

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:只需将Window.DataContext连接到view-model,就像您所做的那样)
和两个自定义类来进行 Package 。

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)-这是阿瓦隆的缺点。只是为了好玩,但你可能需要它,所以我把它留在里面了。
使用AvalonRelayCommand重新连接应用程序路由的命令-对于其他东西,使用您自己的命令实现。这两个类是核心。
您需要使用AvalonTextEditor而不是TextEditor -它只是一个很小的 Package 器-将ContextMenuTextEditor挂钩(除了其他问题,菜单项是suffering,因为缺少visual tree-并且您无法轻松地从中获得任何控件)。我们需要从CommandParameter(设置为ContextMenu)获取TextEditor的引用。这可以通过一些附件属性来完成(w/o覆盖TextEditor),但这样似乎更干净。
在XAML方面--只有一些小的改变--使用 Package 器编辑器--你有一个MenuItem样式,injects是每个命令的正确参数(你可以用其他方式来做,这样更好)。
这不是一个hack-我们只是通过手动调用TextArea命令处理来缩短鼠标处理的缺点。差不多就是这样。
好好享受吧!

9rnv2umw

9rnv2umw2#

可能对大多数人来说已经太晚了,但我在菜单项中使用CommandTarget解决了这个问题:

<MenuItem Header="_Paste" Command="Paste" CommandTarget="{Binding ElementName=avalon, Path=TextArea}"/>

HTH。

相关问题