午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPF、ScrollViewerの中の要素をマウスドラッグ移動しているように見せかける

f:id:gogowaten:20191213133901p:plain

サイズが1024x768画像をScrollViewerの中に置いたImageに表示して
イメージ 1
マウスドラッグ移動で画像の表示位置を変更
画像を動かしているように見えるけど
実際に動かしているのはスクロールバー
 
今回のアプリのダウンロード先
デザイン画面

f:id:gogowaten:20191213133913p:plain

ScrollViewer
┗Image
 
コード

f:id:gogowaten:20191213133925p:plain

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;

namespace _20181112_スクロール_マウスドラッグ
{
    public partial class MainWindow : Window
    {
        Point MyPoint;//マウスクリックの位置用
        public MainWindow()
        {
            InitializeComponent();
            Title = this.ToString();

            string filePath1 = @"D:\ブログ用\チェック用2\NEC_5848_2018_10_27_午後わてん.jpg";
            MyImage.Source = new BitmapImage(new Uri(filePath1));

            MyImage.MouseRightButtonDown += MyImage_MouseRightButtonDown;
            MyImage.MouseRightButtonUp += MyImage_MouseRightButtonUp;
            MyImage.MouseMove += MyImage_MouseMove;
        }
        private void MyImage_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {           
            MyImage.Cursor = Cursors.Arrow;//カーソル形状を矢印に戻す
            //マウスがScrollViewer外になってもドラッグ移動を有効にしたいときだけ必要
            MyImage.ReleaseMouseCapture();
        }
        private void MyImage_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            //クリック位置取得
            MyPoint = e.GetPosition(this);
            //マウスがScrollViewer外になってもドラッグ移動を有効にしたいときだけ必要
            MyImage.CaptureMouse();
        }
        private void MyImage_MouseMove(object sender, MouseEventArgs e)
        {
            //マウスドラッグ移動の距離だけスクロールさせるには
            //(直前のカーソル位置 - 今のカーソル位置) + (スクロールバーのOffset位置)
            //この値をSetOffsetする
            if (e.RightButton == MouseButtonState.Pressed)
            {
                MyImage.Cursor = Cursors.ScrollAll;//カーソル形状を変更
                //今のマウスの座標
                var mouseP = e.GetPosition(this);
                //マウスの移動距離=直前の座標と今の座標の差
                var xd = MyPoint.X - mouseP.X;
                var yd = MyPoint.Y - mouseP.Y;
                //xd *= 2;//2倍速
                //yd *= 2;

                //移動距離+今のスクロール位置
                xd += MyScroll.HorizontalOffset;
                yd += MyScroll.VerticalOffset;
                //スクロール位置の指定
                MyScroll.ScrollToHorizontalOffset(xd);
                MyScroll.ScrollToVerticalOffset(yd);

                MyPoint = mouseP;//直前の座標を今の座標に変更
            }
        }
    }
}
右ボタンでのドラッグ移動にしているのは、マウスが壊れていて左ボダンでのドラッグ移動がまともにできないから
 
 
右クリック位置を動かす「直前の位置」として記録しておいて、動いたら
(直前の位置 - 今の位置) + スクロールバーの位置
この値をスクロールバーに指定すれば期待通りの動きになった
 
 
最初わからんかったところ
今まで要素やコントロールのドラッグ移動させるときのマウスの移動量の計算は
最初のクリック位置と今の位置の差だったけど
今回のスクロールバーを使って動かすのは、これだとうまくできなくて
最初のクリック位置を動くたびに今の位置に更新することで期待通りの動きになった
 
 
よくわからん
クリック位置と今の位置の取得は
//クリック位置取得
MyPoint = e.GetPosition(this);
//今のマウスの座標
var mouseP = e.GetPosition(this);
って基準をthisにしているけどよくわかっていない、最初はこれをMyImageにしたけど合わないみたいでスクロールがガタガタになった、うーん、MyImage上でのマウスイベントだからこれでもいいと思ったんだけどねえ、位置が合わないみたい

 
 
 
前回のに組み合わせて
イメージ 5
できた
わざわざスクロールバーを動かさなくてもいいし
斜め移動もできるから便利
 
デザイン画面は前回と一緒
<Window x:Class="_20181115_2つの画像をドラッグ移動とスクロール同期.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_20181115_2つの画像をドラッグ移動とスクロール同期"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <Grid Margin="0 10 0 0">
    <Grid.ColumnDefinitions>
      <ColumnDefinition/>
      <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="20"/>
      <RowDefinition/>
    </Grid.RowDefinitions>

    <StackPanel Grid.ColumnSpan="2" Grid.Row="0" Grid.Column="0" Grid.RowSpan="1"
                Orientation="Horizontal">
      <TextBlock Text="{Binding ElementName=SliderScale, Path=Value, StringFormat=表示倍率 : 0}" Margin="10 0"/>
      <Slider Name="SliderScale" Value="1.0" Minimum="1.0" Maximum="5.0" SmallChange="1.0" LargeChange="1.0"
              IsMoveToPointEnabled="False" IsSnapToTickEnabled="True"
              Width="100"/>
      <TextBlock Text="{Binding ElementName=MyCanvas1, Path=Width, StringFormat=横 : 0}" Margin="10 0"/>
      <TextBlock Text="{Binding ElementName=MyCanvas1, Path=Height, StringFormat=縦 : 0}" Margin="10 0"/>

      <TextBlock Text="{Binding ElementName=MyScroll1,Path=VerticalOffset,StringFormat= vOffset \= 0}" Margin="10,0"/>
      <TextBlock Text="{Binding ElementName=MyScroll1,Path=ScrollableHeight,StringFormat= height \= 0}" Margin="10,0"/>
      <TextBlock Name="MyText"/>
    </StackPanel>

    <ScrollViewer Name="MyScroll1" Grid.Column="0" Grid.Row="1"
                  VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
      <Canvas Name="MyCanvas1">
        <Image Name="MyImage1" Stretch="None"/>
      </Canvas>
    </ScrollViewer>

    <ScrollViewer Name="MyScroll2" Grid.Column="1" Grid.Row="1"
                  VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
      <Canvas Name="MyCanvas2">
        <Image Name="MyImage2" Stretch="None"/>
      </Canvas>
    </ScrollViewer>

  </Grid>
</Window>
 
コード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace _20181115_2つの画像をドラッグ移動とスクロール同期
{
    public partial class MainWindow : Window
    {
        ScaleTransform MyScale;//拡大率用
        Point MyPoint;// マウスクリックの位置用

        public MainWindow()
        {
            InitializeComponent();
            Title = this.ToString();

            Loaded += MainWindow_Loaded;
            MyInitialize();
        }
        //初期処理
        private void MyInitialize()
        {
            //表示する画像ファイルのパス
            string filePath1 = @"D:\ブログ用\チェック用2\NEC_5899_2018_10_28_午後わてん_.jpg";
            string filePath2 = @"D:\ブログ用\チェック用2\NEC_5899_2018_10_28_午後わてん_16color.png";

            //Imageに画像表示
            MyImage1.Source = new BitmapImage(new Uri(filePath1));
            MyImage2.Source = new BitmapImage(new Uri(filePath2));


            SliderScale.ValueChanged += SliderScale_ValueChanged;
            MyScroll1.ScrollChanged += MyScroll1_ScrollChanged;
            MyScroll2.ScrollChanged += MyScroll2_ScrollChanged;
            MyImage1.MouseRightButtonDown += MyImage1_MouseRightButtonDown;
            MyImage1.MouseRightButtonUp += MyImage1_MouseRightButtonUp;
            MyImage1.MouseMove += MyImage1_MouseMove;
            MyImage2.MouseRightButtonDown += MyImage2_MouseRightButtonDown;
            MyImage2.MouseRightButtonUp += MyImage2_MouseRightButtonUp;
            MyImage2.MouseMove += MyImage2_MouseMove;
            //拡大倍率用
            MyScale = new ScaleTransform();
            MyImage1.RenderTransform = MyScale;
            MyImage2.RenderTransform = MyScale;
        }
        private void MyImage2_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.RightButton == MouseButtonState.Pressed)
            {
                MyImage2.Cursor = Cursors.ScrollAll;
                var mouseP = e.GetPosition(this);          
                var xd = MyPoint.X - mouseP.X;
                var yd = MyPoint.Y - mouseP.Y;
                xd += MyScroll2.HorizontalOffset;
                yd += MyScroll2.VerticalOffset;             
                MyScroll2.ScrollToHorizontalOffset(xd);
                MyScroll2.ScrollToVerticalOffset(yd);
                MyPoint = mouseP;
            }
        }
        private void MyImage2_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
            MyImage2.ReleaseMouseCapture();
            MyImage2.Cursor = Cursors.Arrow;
        }
        private void MyImage2_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            MyPoint = e.GetPosition(this);            
            MyImage2.CaptureMouse();
        }
        private void MyImage1_MouseMove(object sender, MouseEventArgs e)
        {
                //マウスの移動距離だけスクロールさせる
            if (e.RightButton == MouseButtonState.Pressed)
            {
                MyImage1.Cursor = Cursors.ScrollAll;
                //今のマウスの座標
                var mouseP = e.GetPosition(this);
                //マウスの移動距離=直前の座標と今の座標の差
                var xd = MyPoint.X - mouseP.X;
                var yd = MyPoint.Y - mouseP.Y;
                //xd *= 2;//2倍速
                //yd *= 2;
                //移動距離+今のスクロール位置
                xd += MyScroll1.HorizontalOffset;
                yd += MyScroll1.VerticalOffset;
                //スクロール位置の指定
                MyScroll1.ScrollToHorizontalOffset(xd);
                MyScroll1.ScrollToVerticalOffset(yd);

                MyPoint = mouseP;//直前の座標を今の座標に変更
            }
        }
        private void MyImage1_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
            //マウスがScrollViewer外になってもドラッグ移動を有効にしたいときだけ必要
            MyImage1.ReleaseMouseCapture();
            MyImage1.Cursor = Cursors.Arrow;
        }
        private void MyImage1_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            MyPoint = e.GetPosition(this);
            //マウスがScrollViewer外になってもドラッグ移動を有効にしたいときだけ必要
            MyImage1.CaptureMouse();
        }
        //アプリ起動完了時に表示された画像サイズを取得してCanvasサイズに指定する
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            //Canvasサイズを画像のサイズにして
            //ScrollViewerによるスクロールバーを正しく表示する
            MyCanvas1.Width = MyImage1.ActualWidth;
            MyCanvas1.Height = MyImage1.ActualHeight;
            MyCanvas2.Width = MyImage2.ActualWidth;
            MyCanvas2.Height = MyImage2.ActualHeight;
            //画像の拡大方法を最近傍法に指定、無指定なら線形補間
            RenderOptions.SetBitmapScalingMode(MyImage1, BitmapScalingMode.NearestNeighbor);
            RenderOptions.SetBitmapScalingMode(MyImage2, BitmapScalingMode.NearestNeighbor);

        }

        //     UWPのScrollViewerでスクロール位置の同期を行うメモ
        //http://studio-geek.com/archives/857
        private void MyScroll2_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            //値が双方で異なるときだけ更新
            if (e.VerticalOffset != MyScroll1.VerticalOffset)
            {
                MyScroll1.ScrollToVerticalOffset(e.VerticalOffset);
            }
            if (e.HorizontalOffset != MyScroll1.HorizontalOffset)
            {
                MyScroll1.ScrollToHorizontalOffset(e.HorizontalOffset);
            }
        }
        private void MyScroll1_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            if (e.VerticalOffset != MyScroll2.VerticalOffset)
            {
                MyScroll2.ScrollToVerticalOffset(e.VerticalOffset);
            }
            if (e.HorizontalOffset != MyScroll2.HorizontalOffset)
            {
                MyScroll2.ScrollToHorizontalOffset(e.HorizontalOffset);
            }
        }
        //拡大倍率変更時はImageを乗せているCanvasのサイズを変更する
        private void SliderScale_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            //ScaleTransformの拡大倍率変更
            MyScale.ScaleX = e.NewValue;
            MyScale.ScaleY = e.NewValue;
            //拡大後Imageのサイズを取得
            var bounds = MyScale.TransformBounds(new Rect(MyImage1.RenderSize));

            //Imageが乗っかっているCanvasのサイズを変更すると
            //正しく表示され、スクロールバーも期待通りになる
            MyCanvas1.Height = bounds.Height;
            MyCanvas1.Width = bounds.Width;

            //Image2も同様
            bounds = MyScale.TransformBounds(new Rect(MyImage2.RenderSize));
            MyCanvas2.Height = bounds.Height;
            MyCanvas2.Width = bounds.Width;

        }

    }
}
 
 
 
 
 
 
 

関連記事
前回、2018/11/15
WPF、スクロールバーの同期、2つの画像を並べて拡大して見比べたい、ScrollViewer ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15754500.html