none
コマンド実装側でViewのMouseイベントを取得したい RRS feed

  • 質問

  • いつもお世話になっております。

    Window上で右クリックすると、クリックした位置にLabelをnewするPGがあります。

    View側XAMLに以下のようなコンテキストメニューがございます↓

    <ContextMenuService.ContextMenu>
            <ContextMenu>
                <MenuItem Header="この位置に何か表示!" PreviewMouseLeftButtonDown="TEST_Click"/>
            </ContextMenu>
     </ContextMenuService.ContextMenu>

    TEST_Click()は、こんな感じです↓
    private void TEST_Click(object sender, MouseButtonEventArgs e)
            {
                //マウスの座標位置取得
                System.Windows.Point point = e.GetPosition(this.myCanvas);
     

                Label lbl = new Label();


                Canvas.SetLeft(lbl, point.X);
                Canvas.SetTop(lbl, point.Y);

                this.myCanvas.Children.Add(lbl);



            }


    このクリックイベントをコマンドにしようと思ったのですが、
    マウスイベントであるMouseButtonEventArgsを取得する方法が分かりません。

    コマンド側(ViewModel側)ではどのようにすれば取得出来るでしょうか?

    2012年12月20日 9:44

回答

  • 状況によって使える手段が異なるので、とりあえず2つほど。

    1つはつい最近
    http://social.msdn.microsoft.com/Forums/ja-JP/wpfja/thread/d3d5e2cf-aeba-4b39-8b93-7b16cf1ac110
    でも書いたのですが、BlendSDKのCallMethodBehaviorを使う方法です。
    こちらはイベントハンドラのシグネチャをそのまんま流用できるので使い勝手が良いです。
    ただし.NET Framework4以上が必要です。

    もう1つはCommandParameterにBinding RelativeSource=Selfでコマンドを発生させたオブジェクト自身を渡してやることです。
    さすがにEventArgsは渡せませんが、senderは渡せるのでかなり色々できるハズです。
    こちらはFrameworkを問いません。

    <Window x:Class="WpfApplication6.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:core="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
    
            <Canvas Grid.Column="0" Background="LightGray">
                <ContextMenuService.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="FW4以上ならこっち">
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="PreviewMouseLeftButtonDown">
                                    <core:CallMethodAction TargetObject="{Binding}" MethodName="TEST_Click"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </MenuItem>
                    </ContextMenu>
                </ContextMenuService.ContextMenu>
            </Canvas>
    
            <Canvas Grid.Column="1" Background="LightCyan">
                <ContextMenuService.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="それ以前はこちら" Command="{Binding Path=ClickCommand}">
                            <MenuItem.CommandParameter>
                                <Binding RelativeSource="{RelativeSource Self}"/>
                            </MenuItem.CommandParameter>
                        </MenuItem>
                    </ContextMenu>
                </ContextMenuService.ContextMenu>
            </Canvas>
        </Grid>
    </Window>
    


        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new ViewModel();
            }
        }
    


        class ViewModel
        {
    	    public ICommand ClickCommand { get; set; }
    
            public ViewModel()
    	    {
                this.ClickCommand = new RelayCommand(param => { Click(param); });
    	    }
    
    	    public void Click(object parameter)
    	    {
                ContextMenu menu = FindVisualParent<ContextMenu>((DependencyObject)parameter);
                Canvas canvas = menu.PlacementTarget as Canvas;
    
                //マウスの座標位置取得
                System.Windows.Point point = Mouse.GetPosition(canvas);
    
    
                Label lbl = new Label() { Content = "くりっく" };
    
    
                Canvas.SetLeft(lbl, point.X);
                Canvas.SetTop(lbl, point.Y);
    
                canvas.Children.Add(lbl);
            }
    
    
            public void TEST_Click(object sender, MouseButtonEventArgs e)
            {
                ContextMenu menu = FindVisualParent<ContextMenu>((DependencyObject)sender);
                Canvas canvas = menu.PlacementTarget as Canvas;
    
                //マウスの座標位置取得
                System.Windows.Point point = e.GetPosition(canvas);
    
    
                Label lbl = new Label() { Content = "Click" };
    
    
                Canvas.SetLeft(lbl, point.X);
                Canvas.SetTop(lbl, point.Y);
    
                canvas.Children.Add(lbl);
            }
    
            public static T FindVisualParent<T>(DependencyObject d) where T : DependencyObject
            {
                if (d == null) return null;
    
                try
                {
                    DependencyObject root = VisualTreeHelper.GetParent(d);
    
                    if (root != null && root is T)
                    {
                        return root as T;
                    }
                    else
                    {
                        T parent = FindVisualParent<T>(root);
                        if (parent != null) return parent;
                    }
    
                    return null;
                }
                catch
                {
                    if (d is FrameworkElement)
                    {
                        FrameworkElement element = (FrameworkElement)d;
                        if (element.Parent is T) return element.Parent as T;
                        return FindVisualParent<T>(element.Parent);
                    }
                    else if (d is FrameworkContentElement)
                    {
                        FrameworkContentElement element = (FrameworkContentElement)d;
                        if (element.Parent is T) return element.Parent as T;
                        return FindVisualParent<T>(element.Parent);
                    }
                    else
                    {
                        return null;
                    }
                }
            }
        }
    


        public class RelayCommand : ICommand
        {
            readonly Action<object> _execute;
            readonly Predicate<object> _canExecute;
    
            public RelayCommand(Action<object> execute)
                : this(execute, null)
            {
            }
    
            public RelayCommand(Action<object> execute, Predicate<object> canExecute)
            {
                if (execute == null) throw new ArgumentNullException("execute");
    
                _execute = execute;
                _canExecute = canExecute;
            }
    
            [DebuggerStepThrough]
            public bool CanExecute(object parameter)
            {
                return _canExecute == null ? true : _canExecute(parameter);
            }
    
            public event EventHandler CanExecuteChanged
            {
                add { CommandManager.RequerySuggested += value; }
                remove { CommandManager.RequerySuggested -= value; }
            }
    
            public void Execute(object parameter)
            {
                _execute(parameter);
            }
        }
    

    あと、マウスの位置情報はMouseButtonEventArgsでなくてもMouse.GetPosition(静的メソッド)でも取れますよ。
    なお、FindVisualParentとRelayCommandは楽するために入れてるだけで今回の手法の本質ではありません。念のため。

    • 回答としてマーク sumi_sumi 2012年12月21日 3:49
    2012年12月20日 15:02
  • こんにちは。このスレッド、ウオッチしてました。

    > ViewModelのClick()の中、以下の行でnullが返ってきて先へ進めません。
    > 何故でしょう??

    > Canvas canvas = menu.PlacementTarget as Canvas;

    開発環境はXPでしょうか?
    Windows 7・XP の両方でみっとさんが提示されたサンプルを試しましたが、いずれも正常に動作しました。
    menu.PlacementTarget は Windows 7・XP とも Canvas のインスタンスがきちんと返ってきます。
    XP 側プロジェクトの .NET Framework を 3.5 に変更してみましたが、これも Canvas のインスタンスは返ってきます。
    どこかコーディングミスされていませんか?

    もしCanvas以外の要素にコンテキストメニュー配置してるなら、ヌル返りますよね?


    ひらぽん http://d.hatena.ne.jp/hilapon/


    2012年12月21日 2:46
    モデレータ

すべての返信

  • 状況によって使える手段が異なるので、とりあえず2つほど。

    1つはつい最近
    http://social.msdn.microsoft.com/Forums/ja-JP/wpfja/thread/d3d5e2cf-aeba-4b39-8b93-7b16cf1ac110
    でも書いたのですが、BlendSDKのCallMethodBehaviorを使う方法です。
    こちらはイベントハンドラのシグネチャをそのまんま流用できるので使い勝手が良いです。
    ただし.NET Framework4以上が必要です。

    もう1つはCommandParameterにBinding RelativeSource=Selfでコマンドを発生させたオブジェクト自身を渡してやることです。
    さすがにEventArgsは渡せませんが、senderは渡せるのでかなり色々できるハズです。
    こちらはFrameworkを問いません。

    <Window x:Class="WpfApplication6.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
            xmlns:core="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
    
            <Canvas Grid.Column="0" Background="LightGray">
                <ContextMenuService.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="FW4以上ならこっち">
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="PreviewMouseLeftButtonDown">
                                    <core:CallMethodAction TargetObject="{Binding}" MethodName="TEST_Click"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </MenuItem>
                    </ContextMenu>
                </ContextMenuService.ContextMenu>
            </Canvas>
    
            <Canvas Grid.Column="1" Background="LightCyan">
                <ContextMenuService.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="それ以前はこちら" Command="{Binding Path=ClickCommand}">
                            <MenuItem.CommandParameter>
                                <Binding RelativeSource="{RelativeSource Self}"/>
                            </MenuItem.CommandParameter>
                        </MenuItem>
                    </ContextMenu>
                </ContextMenuService.ContextMenu>
            </Canvas>
        </Grid>
    </Window>
    


        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new ViewModel();
            }
        }
    


        class ViewModel
        {
    	    public ICommand ClickCommand { get; set; }
    
            public ViewModel()
    	    {
                this.ClickCommand = new RelayCommand(param => { Click(param); });
    	    }
    
    	    public void Click(object parameter)
    	    {
                ContextMenu menu = FindVisualParent<ContextMenu>((DependencyObject)parameter);
                Canvas canvas = menu.PlacementTarget as Canvas;
    
                //マウスの座標位置取得
                System.Windows.Point point = Mouse.GetPosition(canvas);
    
    
                Label lbl = new Label() { Content = "くりっく" };
    
    
                Canvas.SetLeft(lbl, point.X);
                Canvas.SetTop(lbl, point.Y);
    
                canvas.Children.Add(lbl);
            }
    
    
            public void TEST_Click(object sender, MouseButtonEventArgs e)
            {
                ContextMenu menu = FindVisualParent<ContextMenu>((DependencyObject)sender);
                Canvas canvas = menu.PlacementTarget as Canvas;
    
                //マウスの座標位置取得
                System.Windows.Point point = e.GetPosition(canvas);
    
    
                Label lbl = new Label() { Content = "Click" };
    
    
                Canvas.SetLeft(lbl, point.X);
                Canvas.SetTop(lbl, point.Y);
    
                canvas.Children.Add(lbl);
            }
    
            public static T FindVisualParent<T>(DependencyObject d) where T : DependencyObject
            {
                if (d == null) return null;
    
                try
                {
                    DependencyObject root = VisualTreeHelper.GetParent(d);
    
                    if (root != null && root is T)
                    {
                        return root as T;
                    }
                    else
                    {
                        T parent = FindVisualParent<T>(root);
                        if (parent != null) return parent;
                    }
    
                    return null;
                }
                catch
                {
                    if (d is FrameworkElement)
                    {
                        FrameworkElement element = (FrameworkElement)d;
                        if (element.Parent is T) return element.Parent as T;
                        return FindVisualParent<T>(element.Parent);
                    }
                    else if (d is FrameworkContentElement)
                    {
                        FrameworkContentElement element = (FrameworkContentElement)d;
                        if (element.Parent is T) return element.Parent as T;
                        return FindVisualParent<T>(element.Parent);
                    }
                    else
                    {
                        return null;
                    }
                }
            }
        }
    


        public class RelayCommand : ICommand
        {
            readonly Action<object> _execute;
            readonly Predicate<object> _canExecute;
    
            public RelayCommand(Action<object> execute)
                : this(execute, null)
            {
            }
    
            public RelayCommand(Action<object> execute, Predicate<object> canExecute)
            {
                if (execute == null) throw new ArgumentNullException("execute");
    
                _execute = execute;
                _canExecute = canExecute;
            }
    
            [DebuggerStepThrough]
            public bool CanExecute(object parameter)
            {
                return _canExecute == null ? true : _canExecute(parameter);
            }
    
            public event EventHandler CanExecuteChanged
            {
                add { CommandManager.RequerySuggested += value; }
                remove { CommandManager.RequerySuggested -= value; }
            }
    
            public void Execute(object parameter)
            {
                _execute(parameter);
            }
        }
    

    あと、マウスの位置情報はMouseButtonEventArgsでなくてもMouse.GetPosition(静的メソッド)でも取れますよ。
    なお、FindVisualParentとRelayCommandは楽するために入れてるだけで今回の手法の本質ではありません。念のため。

    • 回答としてマーク sumi_sumi 2012年12月21日 3:49
    2012年12月20日 15:02
  • みっとさま

    有難う御座います。

    とても簡単にはいかないんですね(*_*)))

    XPがメインなので後者の方法でいきたいと思います。


    ViewModelのClick()の中、以下の行でnullが返ってきて先へ進めません。
    何故でしょう??

    Canvas canvas = menu.PlacementTarget as Canvas;
    2012年12月21日 0:49
  • こんにちは。このスレッド、ウオッチしてました。

    > ViewModelのClick()の中、以下の行でnullが返ってきて先へ進めません。
    > 何故でしょう??

    > Canvas canvas = menu.PlacementTarget as Canvas;

    開発環境はXPでしょうか?
    Windows 7・XP の両方でみっとさんが提示されたサンプルを試しましたが、いずれも正常に動作しました。
    menu.PlacementTarget は Windows 7・XP とも Canvas のインスタンスがきちんと返ってきます。
    XP 側プロジェクトの .NET Framework を 3.5 に変更してみましたが、これも Canvas のインスタンスは返ってきます。
    どこかコーディングミスされていませんか?

    もしCanvas以外の要素にコンテキストメニュー配置してるなら、ヌル返りますよね?


    ひらぽん http://d.hatena.ne.jp/hilapon/


    2012年12月21日 2:46
    モデレータ
  • ひらぽんさま

    有難う御座います。

    仰る通りでした。

    Canvas以外の要素になっていました!すいません、有難う御座います。

    ただ、今度は右クリック自体が効かなくなったので再度見てみます。。。

    2012年12月21日 3:31
  • ひらぽんさま

    分かりました。

    コンテンツメニューで表示される色が白、Canvasも白で表示されていないように見えているだけでした。

    ちゃんとみっとさまのサンプルはCanvasに色付けされていました。これが抜けているのが原因でした。すいません、有難う御座います。

    2012年12月21日 3:49