4

プロジェクトで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;
    }
 }

および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アイテムの制御コマンドに接続するにはどうすればよいですか?

4

1 に答える 1

6

これは、私が同様の問題を乗り越えるために使用したものです-人々に役立つことを願っています(この一般的なロジックは、Avalonエディターに関連するさまざまな問題に適用できます)...

実際に起こることは、おそらくアバロンのせいです(などとの組み合わせでListItem)。それはマウスの処理を台無しにし、私はフォーカスを推測しています(これはTextAreafor コマンドにありCanExecute、動作する必要があります.

これmouse handlingが問題です-windows context menuキーを押すだけで、コマンドが有効になっている通常のメニューがポップアップ表示されます。Avalon エディターには複雑なマウス/キー処理があります (優れたエディターを作成するのは困難です) - キーボードではfocus、TextArea で明示的に ' ' を実行します。実際 CanCutOrCopyに. _ 「キーボード」メニューの場合、最初にそこに入り、次にポップアップします。「マウス」の場合、ポップアップし、終了時に(そのメソッドに入ります)をチェックします。それはすべて間違っています!Editing/EditingCommandHandler.csApplicationCommands.CopyCanExecute

そして正誤表…

独自のコマンドに問題はありません。コマンドを通常どおりに公開するだけで、すべてが機能するはずです。

の場合ApplicationCommands(つまりRoutedCommand) が適切に配線されていExecuteませCanExecuteTextArea。コマンドを独自のラッパーに入れる必要があることを修正するにrewireは - 基本的に 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あなたがしたように、ビューモデルに接続するだけです)

これをラップするには、2 つのカスタム クラスが必要です。

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アプリケーション ルーティング コマンドを再配線するために使用します。他のものについては、独自のコマンド実装を使用します。これらの 2 つのクラスがコアです。

TextEditor の代わりに使用する必要がありますAvalonTextEditor- これはほんの小さなラッパーです - に接続するContextMenuにはTextEditor(他の問題は別として、メニュー項目がsuffering不足visual treeしているため、簡単にコントロールを取得することはできません)。そしてTextEditorCommandParameter( a に設定されているContextMenu)からへの参照を取得する必要があります。これは、いくつかの添付プロパティ (TextEditor をオーバーライドせずに) だけで行うこともできましたが、この方法の方がすっきりしているように見えます。

XAML 側では、いくつかの小さな変更を加えるだけで、ラッパー エディターを使用して、各コマンドに適切なパラメーターを指定 するMenuItemスタイルを作成できます (別の方法で行うこともできますが、これはより優れていました)。injects

コマンド処理をhack手動で呼び出すことによって、マウス処理の欠点をショートカットしているだけです。TextAreaそれだけです。

楽しみ!

于 2013-04-06T18:30:10.070 に答える