午後わてんのブログ

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

WPF、スクロールバーの同期、2つの画像を並べて拡大して見比べたい、ScrollViewer

同じ大きさの2つの画像を並べて拡大して見比べたい
イメージ 1
右画像は左を16色に減色したもの
 
今回のアプリのダウンロード先
デザイン画面

f:id:gogowaten:20191213132652p:plain

<feff><Window x:Class="_20181111_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:_20181111_2つの画像を拡大とスクロール同期"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="540">
  <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>
25行目から30行目は確認用だからか要らない
 
33行目から
ScrollViewer		(スクロールバー表示)
	┗Canvas		(パネル系要素なら他のでもいいかも)
		┗Image	(画像表示用)
これを左右で2つ
 
 
C#コード
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace _20181111_2つの画像を拡大とスクロール同期
{
    public partial class MainWindow : Window
    {
    
        ScaleTransform MyScale;//拡大率用
        
        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;

            //拡大倍率用
            MyScale = new ScaleTransform();
            MyImage1.RenderTransform = MyScale;
            MyImage2.RenderTransform = MyScale;
        }
        
        //アプリ起動完了時に表示された画像サイズを取得して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;

        }

    }
}
 
 
 
 
スクロールバーの表示
スクロールバーを付けるにはScrollViewerを使う
ScrollViewerは一個だけ中に小要素(コントロール)を入れることができる、今回の小要素はCanvas
ScrollViewerより小要素のサイズが大きいときスクロールバーが表示される
 
 
2つのスクロールバーの同期

f:id:gogowaten:20191213132924p:plain

ScrollViewerのScrollChangedイベント発生時に同期しているだけなんだけど、これがわからなくて参考になったのがこちら
UWPのScrollViewerでスクロール位置の同期を行うメモ
ありがとうございます
 
最初は
1:Scroll1のScrollChangedイベントが発生したのでScroll2の値を変更
2:Scroll2のScrollChangedイベントが発生したのでScroll1の値を変更
3:1に戻る
っていう永久ループだった
こうならないために相手の値と比較して同じだったら変更しないって処理が必要だった、78行目
            if (e.VerticalOffset != MyScroll1.VerticalOffset)
簡単なことだけど、全く思いつかなかったw
 
 
拡大倍率の変更にスクロールバーも同期させる

f:id:gogowaten:20191213132938p:plain

画像の拡大は画像を表示しているImageのRenderTransformにScaleTransformのScaleを変更するだけでいいとして
スクロールバーは小要素のサイズを変更する必要がある
今回の小要素はCanvasなのでこれのサイズを変更
指定するサイズは拡大された後の画像の大きさで、それを取得しているのが
107行目
var bounds = MyScale.TransformBounds(new Rect(MyImage1.RenderSize));
変数の型をvarで取っているけど実際はRectだわ
TransformクラスのTransformBoundsメソッドを使ってImageの拡大後のRect(四角形サイズ)を取得できる
でもこれをつかわなくても、画像の縦横ピクセル数に拡大倍率をかけてもいいかも
取得できた値でCanvasのサイズを変更すると、スクロールバーも変更される
 
イメージ 4
スクロールバーの長さも変更される
 
 
 
うまく動かなかった例1
Canvas要らないんじゃなね?と思って
 
ScrollViewer
	┗Canvas
		┗Image 
これを左だけCanvasを使わないで
 
ScrollViewer
	┗Image
 
こうしてみたら
イメージ 8
拡大倍率を変更しなければ正しく表示、正しい動作
だけど
拡大倍率を変更して2倍にすると
イメージ 7
画像は拡大されたけどスクロールバーが拡大に対応していない
これはImageのサイズを変更していないからなので
変更するようにして
イメージ 9
109,110行目でImageのサイズ変更処理
こうしてみたら今度は
 
イメージ 6
スクロールバーは正しい動作だけど画像が表示されない
 
なのでScrollViewerに中に直接Imageを入れるよりもCanvasを挟んだほうがいいみたいだった
 
 
うまく動かなかった例2
スクロールバーの同期はBinding使えばいいんじゃね?
イメージ 10
こうして2つのScrollViewerのVerticalOffsetをバインディングしようとしたんだけど
実行したら
イメージ 11
なんかエラーになった
 
TextBlockのTextにはバインディングできる
イメージ 12
双方向をやめて、TextBlockのTextにバインディングしてみたら
 
イメージ 13
これは正しく動作した
うーん、ScrollViewer同士もできたらいいのにねえ
 

 
減色アプリももう少し便利にしたい、作り直したいなあと思って
イメージ 14
減色前後を見比べたかった
 
参照したところ
UWPのScrollViewerでスクロール位置の同期を行うメモ
http://studio-geek.com/archives/857
 
 
ギットハブ
 
 
関連記事
次、2018/11/16
WPF、ScrollViewerの中の要素をマウスドラッグ移動しているように見せかける ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15755956.html
 
 
 
2018/4/10
Owner指定するタイミング、別WindowとMainWindowとの連携、カラーピッカーを追加してパレットの色変更 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15454469.html
 
半年後2019/03/11
以前イベントで処理していたのをBindingにしてC#XAMLで書いてみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15899571.html