LoginSignup
dhq_boiler
@dhq_boiler

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【WPF】ビヘイビアのイベントPreviewKeyDown,PreviewKeyUpが発火しない

Q&AClosed

解決したいこと

C# & WPFでベクターグラフィックスドローイングツールを開発しています。GRAPHERという名前です。

2021-06-14.png

※下にソースコードへの案内を記載しております。よろしければそちらを参照ください。

このツールの、画像を貼り付ける機能を実装しようとしているところです。画面左のツールから「picture」をクリックして、現れるファイルダイアログで、貼り付けたい画像の場所を指定して、実際にキャンバスに画像をドラッグで描くわけです。

単純な画像を貼り付ける機能は簡単に実装できましたが、今度はShiftキーを押している間は、貼り付けようとする画像がアスペクト比が正しくなるように拡大・縮小して描画するようにしたいです。
そこで、コミット0e517deのように実装しました。

発生している問題

しかし、画面上でShiftキーを押下しても、アスペクト比が正しいサイズで描画するようになっていません。
どうやら、PictureBehaviorクラスのPreviewKeyDownイベントハンドラとPreviewKeyUpイベントハンドラが呼び出されていないようです。具体的に言うと、Shiftキーを押したら"set Breakpoint but not fire this event"と書かれているメソッドを実行してほしいのです。

internal class PictureBehavior : Behavior<DesignerCanvas>
    {
        private Point? _pictureDrawingStartPoint = null;
        private string _filename;
        private bool _LeftShiftKeyIsPressed;
        private bool _RightShiftKeyIsPressed;
        public PictureBehavior(string filename)
        {
            _filename = filename;
        }

        protected override void OnAttached()
        {
            this.AssociatedObject.MouseDown += AssociatedObject_MouseDown;
            this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
            this.AssociatedObject.PreviewKeyDown += AssociatedObject_PreviewKeyDown;
            this.AssociatedObject.PreviewKeyUp += AssociatedObject_PreviewKeyUp;
            base.OnAttached();
        }

        protected override void OnDetaching()
        {
            this.AssociatedObject.MouseDown -= AssociatedObject_MouseDown;
            this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
            this.AssociatedObject.PreviewKeyDown -= AssociatedObject_PreviewKeyDown;
            this.AssociatedObject.PreviewKeyUp -= AssociatedObject_PreviewKeyUp;
            base.OnDetaching();
        }

        private void AssociatedObject_PreviewKeyUp(object sender, System.Windows.Input.KeyEventArgs e)
        {
            //set Breakpoint but not fire this event
            if (e.Key == Key.LeftShift)
            {
                _LeftShiftKeyIsPressed = false;
            }
            if (e.Key == Key.RightShift)
            {
                _RightShiftKeyIsPressed = false;
            }
        }

        private void AssociatedObject_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            //set Breakpoint but not fire this event
            if (e.Key == Key.LeftShift)
            {
                _LeftShiftKeyIsPressed = true;
            }
            if (e.Key == Key.RightShift)
            {
                _RightShiftKeyIsPressed = true;
            }
        }

        private void AssociatedObject_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            var canvas = AssociatedObject as DesignerCanvas;
            if (canvas.SourceConnector == null)
            {
                if (e.LeftButton != MouseButtonState.Pressed)
                    _pictureDrawingStartPoint = null;

                if (_pictureDrawingStartPoint.HasValue)
                {
                    AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(canvas);
                    if (adornerLayer != null)
                    {
                        PictureAdorner adorner = new PictureAdorner(canvas, _pictureDrawingStartPoint, _filename, _LeftShiftKeyIsPressed, _RightShiftKeyIsPressed);
                        if (adorner != null)
                        {
                            adornerLayer.Add(adorner);
                        }
                    }
                }
            }
            e.Handled = true;
        }

        private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (e.Source == AssociatedObject)
                {
                    _pictureDrawingStartPoint = e.GetPosition(AssociatedObject);

                    e.Handled = true;
                }
            }
        }
    }

PictureBehaviorクラスのPreviewKeyDownイベントハンドラとPreviewKeyUpイベントハンドラが呼び出されるようにするには何が足りていないのでしょうか。

ちなみに、キャンバスに当たるクラスはDesignerCanvasクラスで、これはDiagramControl.xamlで以下のように使用されています。

                <DockPanel>
                    <DockPanel.LayoutTransform>
                        <MatrixTransform />
                    </DockPanel.LayoutTransform>
                    <Border BorderBrush="Black"
                        BorderThickness="{Binding BorderThickness}"
                        Width="{Binding Width, Converter={StaticResource Plus2Converter}}"
                        Height="{Binding Height, Converter={StaticResource Plus2Converter}}">
                        <ItemsControl ItemContainerStyleSelector="{x:Static selector:DesignerItemsControlItemStyleSelector.Instance}"
                                      ItemsSource="{Binding Items}"
                                      Width="{Binding Width}"
                                      Height="{Binding Height}"
                                      Background="Transparent">
                            :
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <control:DesignerCanvas x:Name="designerCanvas"
                                                            Width="{Binding Width}"
                                                            Height="{Binding Height}"
                                                            AllowDrop="True"
                                                            Background="White" />
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>

Border(BorderBrush=Black)はキャンバス(描画領域)の境界を示しています。

おおもとのItemsControlインスタンスが描画されるデータを管理する(ItemsSource="{Binding Items}")のですが、こいつはBackground=Transparentに明示的に設定しています。

また、ItemsPanelTemplateのDesignerCanvasではBackground=Whiteに指定しています。

なので、BackgroundをTransparentや他の色に明示的に設定する方法は通用しないようです。

ソースコード

GRAPHER
https://github.com/dhq-boiler/grapher

gitリポジトリ
https://github.com/dhq-boiler/grapher.git

何か私の見落とし、致命的な勘違いなど気づいたところがあれば、回答していただけると助かります。よろしくお願いいたします。

0

3Answer

Shiftキー以外(例: Aキー)を押したら"set Breakpoint but not fire this event"と書かれているメソッドが実行されますか?

実行されるようでしたら、Shift、Alt、Ctrlは特殊なキーなので、そのキーだけ押してもイベントが発生しないと思われます。
代わりに、MouseMoveイベントハンドラ内で、Keyboard.GetKeyStates()メソッド等で判定する方法があります。

0

Comments

  1. @dhq_boiler

    Questioner
    早速の回答ありがとうございます。
    >Shiftキー以外(例: Aキー)を押したら"set Breakpoint but not fire this event"と書かれているメソッドが実行されますか?
    Aキーなどをを押下しても、当該メソッドは呼び出されませんでした。

2021-06-15.png

Snoop 3.0.1を使って、(Preview)KeyDownイベントがどこで処理されているか確認できました。

KeyDown on DesignerScrollViewer (ScrollViewer)
KeyDown on DesignerScrollViewer (ScrollViewer)
KeyDown on DesignerScrollViewer (ScrollViewer)
KeyDown on DesignerScrollViewer (ScrollViewer)
KeyDown on DesignerScrollViewer (ScrollViewer)
KeyDown on DesignerScrollViewer (ScrollViewer)
KeyDown on DesignerScrollViewer (ScrollViewer)
KeyDown on DesignerScrollViewer (ScrollViewer)
KeyDown on DesignerScrollViewer (ScrollViewer)
:
:

となっていました。
DesignerScrollViewerインスタンスはDiagramControl.xamlで定義されているもので、ScrollViewerです。

<ScrollViewer Name="DesignerScrollViewer"
                          HorizontalScrollBarVisibility="Auto"
                          VerticalScrollBarVisibility="Auto">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="PreviewMouseWheel">
                        <helper:EventToCommand Command="{Binding MouseWheelCommand}" PassEventArgsToCommand="True" />
                    </i:EventTrigger>
                    <i:EventTrigger EventName="PreviewMouseDown">
                        <helper:EventToCommand Command="{Binding PreviewMouseDownCommand}" PassEventArgsToCommand="True" />
                    </i:EventTrigger>
                    <i:EventTrigger EventName="PreviewMouseUp">
                        <helper:EventToCommand Command="{Binding PreviewMouseUpCommand}" PassEventArgsToCommand="True" />
                    </i:EventTrigger>
                    <i:EventTrigger EventName="MouseMove">
                        <helper:EventToCommand Command="{Binding MouseMoveCommand}" PassEventArgsToCommand="True" />
                    </i:EventTrigger>
                    <i:EventTrigger EventName="MouseLeave">
                        <helper:EventToCommand Command="{Binding MouseLeaveCommand}" PassEventArgsToCommand="True" />
                    </i:EventTrigger>
                    <i:EventTrigger EventName="MouseEnter">
                        <helper:EventToCommand Command="{Binding MouseEnterCommand}" PassEventArgsToCommand="True" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
                <ScrollViewer.Background>
                    :
                </ScrollViewer.Background>
                <DockPanel>
                    <DockPanel.LayoutTransform>
                        <MatrixTransform />
                    </DockPanel.LayoutTransform>
                    <Border BorderBrush="Black"
                        BorderThickness="{Binding BorderThickness}"
                        Width="{Binding Width, Converter={StaticResource Plus2Converter}}"
                        Height="{Binding Height, Converter={StaticResource Plus2Converter}}">
                        <ItemsControl ItemContainerStyleSelector="{x:Static selector:DesignerItemsControlItemStyleSelector.Instance}"
                                      ItemsSource="{Binding Items}"
                                      Width="{Binding Width}"
                                      Height="{Binding Height}"
                                      Background="Transparent">
                            <ItemsControl.Resources>

                                :


                            </ItemsControl.Resources>

                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <control:DesignerCanvas x:Name="designerCanvas"
                                                            Width="{Binding Width}"
                                                            Height="{Binding Height}"
                                                            AllowDrop="True"
                                                            Background="White" />
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                        </ItemsControl>
                    </Border>
                </DockPanel>
                <ScrollViewer.ContextMenu>
                    :
                </ScrollViewer.ContextMenu>

            </ScrollViewer>

DesignerScrollViewerは親でDesignerCanvasは子に当たると考えられるので、DesignerCanvas(のビヘイビア)でイベントをキャプチャーしたかったら、トンネリングのPreviewKeyDownイベントではなく、バブリングのKeyDownイベントを使ったほうが良いのですかね。試してみます。

0

Comments

  1. @dhq_boiler

    Questioner
    *トンネリングイベント(PreviewKeyDown)の伝播
    MainWindow --> DesignerScrollViewer(ScrollViewer):イベントソース

    *バブリングイベント(KeyDown)の伝播
    MainWindow <-- DesignerScrollViewer(ScrollViewer):イベントソース

    Snoopで確認したのは、KeyDownイベントの方でした。この場合、
    1. DesignerScrollViewerでキャプチャー
    2. MainWindowでキャプチャー
    となります。だから、DesignerScrollViewerの子であるDesignerCanvasにはイベントは伝わらず、そのビヘイビアではイベントのキャプチャーができなかったということになります。

代替案を思いついたので記しておきます。

gitを利用できる方は当リポジトリのコミット38e645eをご覧ください。

代替案はキーイベントによってbool変数をスイッチする方法をやめて、
"if (Keyboard.GetKeyStates(Key.LeftShift) & KeyStates.Down) == KeyStates.Down) { ... }"を使いました。

0

Your answer might help someone💌