スクロールビューのスクロール量をSet/Getする
画像のサイズが画面より大きい場合、または画像のサイズ自体は小さくてもそれを拡大表示した場合は、スクロールビューによって表示するのが最適です。
そこで、スクロールビューに付随するスクロールバーのスクロール量(ツマミの位置)を設定したり、取得したりする方法を紹介します。
<Window x:Class="aaa.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:aaa"
mc:Ignorable="d"
Title="MainWindow" Width="720" Height="760">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem x:Name="menuApplication" Header="Application">
<MenuItem x:Name="menuApplicationQuit" Header="Quit" Click="menuApplicationQuit_Click"/>
</MenuItem>
<MenuItem x:Name="menuFile" Header="File">
<MenuItem x:Name="menuFileSave" Header="Save" Click="menuFileSave_Click"/>
</MenuItem>
<MenuItem x:Name="menuCursor" Header="Cursor">
<MenuItem x:Name="menuCursorMoveToCenter" Header="MoveToCenter" Click="menuCursorMoveToCenter_Click" />
<MenuItem x:Name="menuCursorMoveToLeftTop" Header="MoveToLT" Click="menuCursorMoveToLeftTop_Click" />
<MenuItem x:Name="menuCursorMoveToRightBottom" Header="MoveToRB" Click="menuCursorMoveToRightBottom_Click" />
</MenuItem>
</Menu>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button x:Name="tbnFileSave" Content="Save" Width ="48" Height="48" Click="menuFileSave_Click"></Button>
<Separator/>
<Button x:Name="tbnCursorMoveToCenter" Content="MoveToCenter" Width ="96" Height="48" Click="menuCursorMoveToCenter_Click"></Button>
<Button x:Name="tbnCursorMoveToLeftTop" Content="MoveToLT" Width ="96" Height="48" Click="menuCursorMoveToLeftTop_Click"></Button>
<Button x:Name="tbnCursorMoveToRightBottom" Content="MoveToRB" Width ="96" Height="48" Click="menuCursorMoveToRightBottom_Click"></Button>
</ToolBar>
</ToolBarTray>
<StatusBar DockPanel.Dock="Bottom" Height="30">
<Label Name="stbLabel000"/>
<Separator/>
<Label Name="stbLabel001"/>
<Separator/>
<Label Name="stbLabel002"/>
<Separator/>
<Label Name="stbLabel003"/>
<Separator/>
<Label Name="stbLabel004"/>
</StatusBar>
<Border BorderBrush="DarkViolet" BorderThickness="1" >
<ScrollViewer Name="scv000" Background="DarkViolet" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollChanged="OnScrollChanged">
<Grid x:Name="grid000" MouseDown="OnMouseDown" MouseMove="OnMouseMove">
<Image x:Name="image000" Stretch="Uniform"/>
<Canvas x:Name="canvas000" Background="Transparent">
<Line x:Name="cross_hair_horz" Stroke="Aqua" StrokeThickness="2"/>
<Line x:Name="cross_hair_vert" Stroke="Aqua" StrokeThickness="2"/>
</Canvas>
</Grid>
</ScrollViewer>
</Border>
</DockPanel>
</Window>
下記の MainWindow.xaml.cs において、
234行目、水平方向のスクロール量 HorizontalOffset を参照しています。
235行目、垂直方向のスクロール量 VerticalOffset を参照しています。
307行目からは、スクロール量を設定するための private なメソッドを作ってみました。
338行目、ScrollToHorizontalOffset()で、水平方向のスクロール量を設定します。
339行目、ScrollToVerticalOffset()で、垂直方向のスクロール量を設定します。
そのときに与える引数には、スクロールビューで見えている領域の縦横サイズ( ビューポートのサイズとでもいうのでしょうか )を考慮してやるとユーザーフレンドリーな動作になると思います。
329行目、ビューポートの横(水平方向)のサイズは ActualWidth で取得できます。
330行目、ビューポートの縦(垂直方向)のサイズは ActualHeight で取得できます。
335行目、ScrollToHorizontalOffset() に与える引数をビューポートの横サイズを考慮して計算します。
336行目、ScrollToVerticalOffset() に与える引数をビューポートの縦サイズを考慮して計算します。
下記の名前空間の追加をお忘れなく.
using System.Windows.Media.Imaging;
using System.IO;
using Microsoft.Win32;
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.Navigation;
using System.Windows.Shapes;
using System.Windows.Media;
// これの追加を忘れないように. Don't forget to add this sentence.
using System.Windows.Media.Imaging;
using System.IO;
using Microsoft.Win32;
namespace aaa
{
public partial class MainWindow : Window
{
const double DPI_X = 96.0;
const double DPI_Y = 96.0;
byte [] Data000;
WriteableBitmap Bmp000;
int TheX;
int TheY;
// 画像のサイズ.
const int REQ_W = 4096;
const int REQ_H = 2048;
// コサイン波形の周期.
const double CYCLE_HORZ = 32.0;
const double CYCLE_VERT = 8.0;
// 水平垂直加算コサインデータを取得する.
private bool get_data_cos_wave_horz_vert(
byte [] data,
int width,
int height,
double cycle_horz, // 水平方向の周期.
double cycle_vert // 垂直方向の周期.
)
{
int w = width;
int h = height;
double two_pai = 2.0 * Math.PI;
double rate;
double rad;
double tmp;
double [] table_for_a = new double[ h ];
double [] table_for_b = new double[ w ];
const double OFFSET = 1.0;
const double MUL = 0.5;
// テーブルを作る.
for ( int j = 0; j < h; j++ )
{
// ラジアンを算出する.
rate = (double)(j)/(double)( h - 1 );
rad = rate * two_pai;
// ここでコサインの計算をする.
tmp = -( Math.Cos( cycle_vert * rad ));
// 0.0 から 1.0 が格納される.
table_for_a[j] = (( tmp + OFFSET ) * MUL );
}
// テーブルを作る.
for ( int i = 0; i < w; i++ )
{
// ラジアンを算出する.
rate = (double)(i)/(double)( w - 1 );
rad = rate * two_pai;
// ここでコサインの計算をする.
tmp = -( Math.Cos( cycle_horz * rad ));
// 0.0 から 1.0 が格納される.
table_for_b[i] = (( tmp + OFFSET ) * MUL );
}
double a;
double b;
byte level;
int adrs;
for ( int j = 0; j < h; j++ )
{
// 0.0 から 1.0 の範囲をとる.
a = table_for_a[j];
adrs = w * j;
for ( int i = 0; i < w; i++ )
{
// 0.0 から 1.0 の範囲をとる.
b = table_for_b[i];
// 両方を加算すると 0.0 から 2.0 の範囲をとる.
tmp = a + b;
// 0 から 255 の byte 区間にマッピングする.
level = (byte)( Math.Round(( tmp * 0.5 ) * 255.0 ));
// 8bits画像データとして格納する.
data[ adrs ] = level;
adrs++;
}
}
return true;
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
public MainWindow()
{
// これはデフォルトのコード.
InitializeComponent();
// データ領域を確保する.
int w = REQ_W;
int h = REQ_H;
int numpix = w * h;
Data000 = new byte[ numpix ];
// 確保した領域にデータを仕込む.
get_data_cos_wave_horz_vert( Data000, w, h, CYCLE_HORZ, CYCLE_VERT );
// グレースケールパレットの要素数.
const int NUM_ELEMENT_OF_PAL = 256;
// 8bppのビットマップを作るので256要素カラーパレットを作る.
List<Color> list_color = new List<Color>(0);
for ( int k = 0; k < NUM_ELEMENT_OF_PAL; k++ )
{
const byte LEVEL_ALPHA = 0xff;
byte value = ( byte )(k);
list_color.Add( Color.FromArgb( LEVEL_ALPHA, value, value, value ) );
}
// 白黒グレースケールのカラーパレット.
BitmapPalette Palette = new BitmapPalette( list_color );
// 8bppのビットマップを生成する.
PixelFormat pixfmt = PixelFormats.Indexed8;
Bmp000 = new WriteableBitmap( w, h, DPI_X, DPI_Y, pixfmt, Palette );
// -------------------------------------------------------------------
// Bmp000 に WritePixels( Data000 ) するのは OnRender() で実施する.
// -------------------------------------------------------------------
// 画像出力先のサイズを決定する.
image000.Width = Bmp000.PixelWidth;
image000.Height = Bmp000.PixelHeight;
// 画像出力先にビットマップを関係づける.
image000.Source = Bmp000;
// 十字カーソルは真ん中に出現.
TheX = w >> 1;
TheY = h >> 1;
// ウインドウのタイトル.
this.Title = "GazoYaro";
}
protected override void OnRender( DrawingContext dc )
{
// 画像のサイズを取得する.
int w = Bmp000.PixelWidth;
int h = Bmp000.PixelHeight;
// ビットマップに画像データを書き込む.
Int32Rect rct = new Int32Rect( 0, 0, w, h );
int stride_bytes = w * sizeof( byte );
int offset_bytes = 0;
Bmp000.WritePixels( rct, Data000, stride_bytes, offset_bytes );
// カーソル位置の濃度を取得する.
int adrs = ( TheX + ( w * TheY ));
byte the_level = Data000[ adrs ];
// 親要素 Grid000 の座標系から image000 のオフセット量を取得する.
Point img_point = image000.TranslatePoint( new Point( 0, 0 ), grid000 );
double plot_offset_x = img_point.X;
double plot_offset_y = img_point.Y;
// Canvas の座標系は Grid の座標系と同じ.
double plot_x = (double)( TheX + plot_offset_x );
double plot_y = (double)( TheY + plot_offset_y );
// 出力コントロール image000 のサイズを取得する.
double img_w = image000.Width;
double img_h = image000.Height;
// 水平線を引く(十字線の横).
cross_hair_vert.X1 = plot_x;
cross_hair_vert.Y1 = plot_offset_y;
cross_hair_vert.X2 = plot_x;
cross_hair_vert.Y2 = plot_offset_y + img_h;
// 垂直線を引く(十字線の縦).
cross_hair_horz.X1 = plot_offset_x;
cross_hair_horz.Y1 = plot_y;
cross_hair_horz.X2 = plot_offset_x + img_w;
cross_hair_horz.Y2 = plot_y;
// スクロールバーのスクロール量を取得する.
double scroll_x = scv000.HorizontalOffset;
double scroll_y = scv000.VerticalOffset;
// スクロールビューの領域サイズを取得する.
double scv_aw = scv000.ActualWidth;
double scv_ah = scv000.ActualHeight;
// ステータスバーのラベルに座標を表示する.
const String FMT000 = "PlotOffsetXY({0},{1})";;
const String FMT001 = "BmpWH {0}*{1}";
const String FMT002 = "TheXY({0},{1}) is {2}";
const String FMT003 = "ScrollX,ScrollY {0:f1},{1:f1}";
const String FMT004 = "ScrollView size {0:f1}*{1:f1}";
String str000 = String.Format( FMT000, plot_offset_x, plot_offset_y );
String str001 = String.Format( FMT001, Bmp000.PixelWidth, Bmp000.PixelHeight );
String str002 = String.Format( FMT002, TheX, TheY, the_level );
String str003 = String.Format( FMT003, scroll_x, scroll_y );
String str004 = String.Format( FMT004, scv_aw, scv_ah );
stbLabel000.Content = str000;
stbLabel001.Content = str001;
stbLabel002.Content = str002;
stbLabel003.Content = str003;
stbLabel004.Content = str004;
}
private void OnMouseDown(object sender, MouseEventArgs e)
{
// 画像のサイズを取得する.
int w = (int)( Math.Round( image000.Width ) );
int h = (int)( Math.Round( image000.Height ) );
// image000 基準のマウス座標を取得する.
Point pnt = e.GetPosition( image000 );
// ここに入る値はゼロ未満や幅高以上もありうる.
int tmp_x = (int)( Math.Round( pnt.X ) );
int tmp_y = (int)( Math.Round( pnt.Y ) );
// クリックした座標が画像からはみ出ないようにする.
if ( tmp_x < 0 ) { tmp_x = 0; }
if ( tmp_y < 0 ) { tmp_y = 0; }
if ( tmp_x >= w ) { tmp_x = w - 1; }
if ( tmp_y >= h ) { tmp_y = h - 1; }
// ここで代入される値は画像領域の内側であることが保証される.
TheX = tmp_x;
TheY = tmp_y;
// 再描画をうながす、OnRender() が呼ばれる.
this.InvalidateVisual();
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
// 左ボタンが押し下げられているかどうか.
if (( e.LeftButton & MouseButtonState.Pressed ) == MouseButtonState.Pressed )
{
// 押し下げられていたら OnMouseDown を実行する.
this.OnMouseDown( sender, e );
}
}
private void OnScrollChanged( object sender, ScrollChangedEventArgs e )
{
// スクロールバーが動かされたら OnRender() 再描画する.
this.InvalidateVisual();
}
// 所望の位置に水平垂直スクロールバーを移動する.
private bool set_scroll_offset(
ScrollViewer scroll_viewer,
int image_width,
int image_height,
int the_x_on_image,
int the_y_on_image
)
{
if ( scroll_viewer == null ) return false;
if ( image_width <= 0 ) return false;
if ( image_height <= 0 ) return false;
int w = image_width;
int h = image_height;
if ( the_x_on_image < 0 || w <= the_x_on_image ) return false;
if ( the_y_on_image < 0 || h <= the_y_on_image ) return false;
ScrollViewer scv = scroll_viewer;
double scv_aw = scroll_viewer.ActualWidth;
double scv_ah = scroll_viewer.ActualHeight;
int req_x = the_x_on_image;
int req_y = the_y_on_image;
double req_scroll_x = ( req_x - ( scv_aw * 0.5 ));
double req_scroll_y = ( req_y - ( scv_ah * 0.5 ));
scv.ScrollToHorizontalOffset( req_scroll_x );
scv.ScrollToVerticalOffset( req_scroll_y );
return true;
}
private void menuCursorMoveToCenter_Click( object sender, RoutedEventArgs e )
{
int w = Bmp000.PixelWidth;
int h = Bmp000.PixelHeight;
// 画像のまんなか.
TheX = w >> 1;
TheY = h >> 1;
// 所望の位置に水平垂直スクロールバーを移動する.
bool ret = set_scroll_offset( scv000, w, h, TheX, TheY );
if ( !ret )
{
MessageBox.Show( "Error: set_scroll_offset();" );
return;
}
this.InvalidateVisual();
}
private void menuCursorMoveToLeftTop_Click( object sender, RoutedEventArgs e )
{
int w = Bmp000.PixelWidth;
int h = Bmp000.PixelHeight;
// 画像の左上隅 LeftTop.
TheX = 0;
TheY = 0;
// 所望の位置に水平垂直スクロールバーを移動する.
bool ret = set_scroll_offset( scv000, w, h, TheX, TheY );
if ( !ret )
{
MessageBox.Show( "Error: set_scroll_offset();" );
return;
}
this.InvalidateVisual();
}
private void menuCursorMoveToRightBottom_Click( object sender, RoutedEventArgs e )
{
int w = Bmp000.PixelWidth;
int h = Bmp000.PixelHeight;
// 画像の右下隅 RightBottom.
TheX = w - 1;
TheY = h - 1;
// 所望の位置に水平垂直スクロールバーを移動する.
bool ret = set_scroll_offset( scv000, w, h, TheX, TheY );
if ( !ret )
{
MessageBox.Show( "Error: set_scroll_offset();" );
return;
}
this.InvalidateVisual();
}
private void menuFileSave_Click( object sender, RoutedEventArgs e )
{
// デフォルトで表示するファイル名.
String fnm_ini = "default_image";
// ファイルダイアログを生成する.
SaveFileDialog sfd = new SaveFileDialog();
sfd.FileName = fnm_ini;
sfd.Filter = "BMP|*.bmp|ALL|*.*";
// ファイルダイアログを表示する.
bool? ret = sfd.ShowDialog();
if ( ret == false )
{
return; // warning.
}
// ---------------------------
// ここからファイル保存 ------
// ---------------------------
String filepath = sfd.FileName;
FileMode file_mode = FileMode.Create;
FileAccess file_access = FileAccess.Write;
using ( FileStream fs = new FileStream( filepath, file_mode, file_access ))
{
BmpBitmapEncoder enc_bmp = new BmpBitmapEncoder();
BitmapFrame bmf = BitmapFrame.Create( Bmp000 );
enc_bmp.Frames.Add( bmf );
enc_bmp.Save( fs );
fs.Close();
}
}
private void menuApplicationQuit_Click( object sender, RoutedEventArgs e )
{
// ウインドウを閉じてアプリケーションを終了する.
this.Close();
}
}
}
サンプルソースを用意しました。上記のコードのコピーペーストでうまく動かない場合にご利用ください。
スクロールビューのツマミの位置をSet/Getする
ビルドして実行したら、ぜひウインドウ下部のステータスバーにご注目ください。
ウインドウをリサイズしたり、スクロールバーを動かしたりしてみてください。そのときのスクロール量と、ビューポートの縦横サイズがステータスバーにリアルタイム表示されています。
またツールボタンの
MoveToCenter はカーソルが画像の中央に移動します。
MoveToLT はカーソルが画像の左上隅(LeftTop)に移動します。
MoveToRB はカーソルが画像の右下隅(RightBottom)に移動します。
MoveToLT、MoveToRB の実行結果がわかりにくければ、MainWindow.xaml で StrokeThickness の値を 4 とか 8 とか、太く変更してみてください。
<Canvas x:Name="canvas000" Background="Transparent">
<Line x:Name="cross_hair_horz" Stroke="Aqua" StrokeThickness="2"/>
<Line x:Name="cross_hair_vert" Stroke="Aqua" StrokeThickness="2"/>
</Canvas>