午後わてんのブログ

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

WPFとVBでアプリ作る準備その5、スクロールバーの表示、回転とかの変形後のコントロールのサイズ取得

前回は2日前
 
前回の記事
WPFVBでアプリ作る準備その4、コントロールを重ねた時の上下移動(ZOrder)はPanel.SetZIndex ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13913897.html
コントロールの大きさや位置の合わせてスクロールバーを表示する
 
画像をウィンドウの外側に移動したとき
イメージ 1
外側に出たままになっているこれを
 
イメージ 22
スクロールバーを自動で表示する
 
デザイン画面とXAML

f:id:gogowaten:20191025102848p:plain

MainWindow

f:id:gogowaten:20191025102859p:plain

ふたつの赤いところが前回から書き加えたところ
灰色のところは必要ないところ(選択画像を30度回転)
Imports System.IO
Imports System.Windows.Controls.Primitives
Imports System.Windows.Controls.Panel
Imports System.Windows.Controls.Canvas


Class MainWindow
    Private FocusExImage As ExImage '選択中の画像(ExImage)
    Private CollectionExImage As New ObservableCollectionExImage 'ExImageのリストコレクション

    'マウスドラッグ移動
    Private Sub ExImage_DragMove(sender As Object, e As DragDeltaEventArgs)
        SetLeft(sender, e.HorizontalChange + GetLeft(sender))
        SetTop(sender, e.VerticalChange + GetTop(sender))
    End Sub

    'ファイルパスからBitmapImage(画像)を作成して返す
    Private Function GetBitmapImage(filePath As String) As BitmapImage
        Dim bmp As New BitmapImage
        Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read)
            With bmp
                .BeginInit()
                .StreamSource = fs
                .CacheOption = BitmapCacheOption.OnLoad
                .EndInit()
                .Freeze()
            End With
        End Using
        Return bmp
    End Function

    'ExImageを作成して追加
    Private Sub AddThumb(filesPath As String, locate As Point)
        Dim ex As New ExImage(Me)
        CollectionExImage.Add(ex) 'リストコレクションに追加
        SetZIndex(ex, CollectionExImage.Count - 1) 'ZIndexを指定
        canvas1.Children.Add(ex)
        ex.Source = GetBitmapImage(filesPath)
        SetLeft(ex, locate.X) '表示する位置は必須、指定しないとDragdeltaイベントで移動量が取得できない
        SetTop(ex, locate.Y)  '必須
        AddHandler ex.ExDragDelta, AddressOf ExImage_DragMove 'これはマウスドラッグ用
        AddHandler ex.MouseDown, AddressOf ExImage_MouseDown '画像をクリックした時に動かすメソッド
    End Sub

    'ウィンドウに画像ファイルがドロップされた時
    Private Sub MainWindow_Drop(sender As Object, e As DragEventArgs) Handles Me.Drop
        Dim filesPath() As String = e.Data.GetData(DataFormats.FileDrop) 'ファイルパス取得
        Dim locate As New Point(0, 0) 'ExImageを表示する位置
        For i As Integer = 0 To filesPath.Length - 1
            Call AddThumb(filesPath(i), locate) 'ExImage作成表示
            locate.Offset(30, 30) '位置の変更
        Next
    End Sub



    'textBlockの表示更新
    Private Sub kousin()
        tbZIndex.Text = "ZIndex = " & GetZIndex(FocusExImage).ToString
    End Sub
    '1つ上に移動
    Private Sub age_Click(sender As Object, e As RoutedEventArgs) Handles age.Click
        Dim z As Integer = CollectionExImage.IndexOf(FocusExImage)
        Call ZOrder(z, z + 1)
    End Sub
    '1つ下に移動
    Private Sub sage_Click(sender As Object, e As RoutedEventArgs) Handles sage.Click
        Dim z As Integer = CollectionExImage.IndexOf(FocusExImage)
        Call ZOrder(z, z - 1)
    End Sub
    '画像のZOrder指定、ExImageのZIndex指定
    Private Sub ZOrder(Moto As Integer, Saki As Integer)
        If FocusExImage Is Nothing Then Return
        CollectionExImage.Move(Moto, Saki) '移動元Index、移動先Index
        Call kousin()
    End Sub
    '画像クリックした時
    Private Sub ExImage_MouseDown(sender As Object, e As RoutedEventArgs)
        FocusExImage = sender 'クリックしたExImageを記録
        mihon.Source = FocusExImage.Source '見本を表示
        Call kousin() 'textBlockの表示更新
    End Sub



    '位置調整
    Public Sub AjustLocation()
        Dim r As Rect = GetUnion(CollectionExImage)
        canvas1.Width = r.Width
        canvas1.Height = r.Height
        If r.X <> 0 OrElse r.Y <> 0 Then
            For Each ex As ExImage In CollectionExImage
                SetLeft(ex, GetLeft(ex) - r.X)
                SetTop(ex, GetTop(ex) - r.Y)
            Next
        End If
    End Sub

    '    プログラミング Windows 第6版 第10章 WPF編 - 荒井省三のBlog - Site Home - MSDN Blogs
    'http://blogs.msdn.com/b/shozoa/archive/2014/08/22/using-programming-windows-chapter10.aspx
    'ExImageのRectを取得、回転後のRectにも対応
    Private Function GetRect(ex As ExImage) As Rect
        'RenderSize版100.0139
        'Dim cVisual As GeneralTransform = ex.TransformToVisual(canvas1)
        'Dim r As Rect = cVisual.TransformBounds(New Rect(ex.RenderSize))
        'Return r

        'SourceのPixelWidth版100
        Dim gt As GeneralTransform = ex.TransformToVisual(canvas1)
        Dim b As BitmapImage = ex.Source
        Dim r As Rect = gt.TransformBounds(New Rect(New Size(b.PixelWidth, b.PixelHeight)))
        Return r
    End Function

    'すべてのExImageのRectのUnionのRectを取得
    Private Function GetUnion(ex As ObservableCollectionExImage) As Rect
        Dim r As Rect = GetRect(ex(0))
        For i As Integer = 1 To ex.Count - 1
            r = Rect.Union(r, GetRect(ex(i)))
        Next
        Return r
    End Function

    Private Sub kaiten_Click(sender As Object, e As RoutedEventArgs) Handles kaiten.Click
        Dim rtf As New RotateTransform(30)
        FocusExImage.RenderTransform = rtf
    End Sub
End Class
 
 
ExImage

f:id:gogowaten:20191025102918p:plain

ふたつの赤いところが前回から書き加えたところ
Imports System.Windows.Controls.Primitives
Imports System.Windows.Controls.Canvas

Public Class ExImage
    Inherits Image
    Private syoki As Point
    Public Event ExDragDelta(sender As Object, e As DragDeltaEventArgs)
    Private Main As MainWindow

    Public Sub New(o As MainWindow)
        Main = o
    End Sub
    Protected Overrides Sub OnMouseLeftButtonDown(e As MouseButtonEventArgs)
        MyBase.OnMouseLeftButtonDown(e)
        syoki = e.GetPosition(Me)
        Me.CaptureMouse()
    End Sub
    Protected Overrides Sub OnMouseMove(e As MouseEventArgs)
        MyBase.OnMouseMove(e)
        If e.LeftButton = MouseButtonState.Pressed Then
            Dim p As Point = Point.Subtract(e.GetPosition(Me), syoki)
            RaiseEvent ExDragDelta(Me, New DragDeltaEventArgs(p.X, p.Y))
        End If
    End Sub
    Protected Overrides Sub OnMouseUp(e As MouseButtonEventArgs)
        MyBase.OnMouseUp(e)
        Me.ReleaseMouseCapture()
    End Sub
    Protected Overrides Sub OnPreviewMouseLeftButtonUp(e As MouseButtonEventArgs)
        MyBase.OnPreviewMouseLeftButtonUp(e)
        Main.AjustLocation()
    End Sub
End Class


'汎用ジェネリックコレクション その2 ObservableCollection/ReadOnlyObservableCollection (System.Collections.ObjectModel) - Programming/.NET Framework/コレクション - 総武ソフトウェア推進所
'http://smdn.jp/programming/netfx/collections/3_objectmodel_2_observablecollection/
'ObservableCollectionはItemの移動ができる、追加、削除、移動した時のメソッドをOverridesできる
Public Class ObservableCollectionExImage
    Inherits ObjectModel.ObservableCollection(Of ExImage)

    Protected Overrides Sub MoveItem(oldIndex As Integer, newIndex As Integer)
        '移動先のIndexが全画像数より大きいか0未満ならなにもしないで終了
        If newIndex >= Count OrElse newIndex < 0 Then Return
        MyBase.MoveItem(oldIndex, newIndex)
        SetZIndex(Item(oldIndex), oldIndex)
        SetZIndex(Item(newIndex), newIndex)
    End Sub

End Class
 
 
 
スクロールバーを表示する準備

f:id:gogowaten:20191025102944p:plain

デザイン画面でウィンドウの左側はStackPanel
右側がCanvasでここに画像を表示している
今回スクロールバーを表示させたいのはCanvas
これの外側にScrollViewerを設置する
 

f:id:gogowaten:20191025103246p:plain

このCanvas部分を
 

f:id:gogowaten:20191025103300p:plain

こうすると
 
イメージ 5
Canvasにスクロールバーが表示される
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
これでそれぞれ垂直と水平バーの表示をオートにしているので
Canvasの大きさがウィンドウより大きくなった時だけ表示される
試しにCanvasの大きさを300x300にすると
イメージ 6
スクロールバー非表示になる
 
左上に寄せたいから
イメージ 14
CanvasのVerticalAlignmentをTop
HorizontalAlignmentをLeft

f:id:gogowaten:20191025103316p:plain

 
スクロールバーを適切に表示するには
CanvasのSize(大きさ)を指定する必要がある
その大きさは表示されている画像が全てがピッタリ収まる範囲
例えば
イメージ 7
赤枠の範囲が求めたいSize
そのためには画像(を表示しているExImage)それぞれの位置(Locate)とSizeが必要
なので求める順番は
画像サイズ→赤枠サイズ→Canvasサイズ
こうなる
 
画像サイズは
Dim s As New Size(ex.Width, ex.Height)
exは画像を表示しているExImageコントロール
これでいいような気もするんだけど例えば
100x100サイズの画像を表示している状態のExImageから
イメージ 9

サイズを取得できるプロパティはいくつかあって

RenderSize、DesiredSize、ActualWidthとActualHeight
これらは3つとも100.0139573とか微妙な値
Sourceとなっている画像自体から得られる
PixelHeightとPixelWidthは100ってあるからこれで良さそうだから
        Dim b As BitmapImage = ex.Source
        Dim s As New Size(b.PixelWidth, b.PixelHeight)
これでいいかと思ったら今度は
画像を回転した時には対応できなくて
イメージ 8
こうなってしまう
 
ググッて
プログラミング Windows 第6版 第10章 WPF編 - 荒井省三のBlog - Site Home - MSDN Blogs
http://blogs.msdn.com/b/shozoa/archive/2014/08/22/using-programming-windows-chapter10.aspx
ここの「10.14(P469) 要素の配置先の取得」
TransformToVisualとTransformBoundsってのを真似して
 
        Dim cVisual As GeneralTransform = ex.TransformToVisual(canvas1)
        Dim r As Rect = cVisual.TransformBounds(New Rect(ex.RenderSize))
イメージ 10
できた
GeneralTransformのTransformBoundsメソッドで取得できる感じ?
迷うのがTransformBoundsに渡す引数のRectのSizeを
100.0139か100のどちらにするか
結果も違ってくる
イメージ 11
rが100.0139を渡した場合、r2が100の場合
rのサイズは 136.621
r2のサイズは 136.602
誤差みたいなものだけどどっちのほうがいいかなあ
イメージ 12
見た目だと全く同じw
でもこれで画像(ExImage)のサイズとついでに位置も取得できた
両方書いたけどすっきりする100の方かなあ↓
    Private Function GetRect(ex As ExImage) As Rect
        'RenderSize版100.0139
        'Dim cVisual As GeneralTransform = ex.TransformToVisual(canvas1)
        'Dim r As Rect = cVisual.TransformBounds(New Rect(ex.RenderSize))
        'Return r


        'SourceのPixelWidth版100
        Dim gt As GeneralTransform = ex.TransformToVisual(canvas1)
        Dim b As BitmapImage = ex.Source
        Dim r As Rect = gt.TransformBounds(New Rect(New Size(b.PixelWidth, b.PixelHeight)))
        Return r
    End Function
Rectは位置とサイズを入れておける、WindowsフォームアプリでいうRectangle
ex.TransformToVisual(canvas1)
これでcanvas1におけるExImageのGeneralTransformの何かを取得している?
取得したGeneralTransformのTransformBoundsメソッドに渡すRectの
位置は0,0、サイズはRenderSizeの方にしてみた
これで赤枠になる位置とサイズ範囲のRectが得られる
 
 
次は複数画像がぴったり収まるサイズの取得
イメージ 15
赤枠になる範囲の取得
これはRectクラスのUnionメソッドで取得できる
Rect.Union(Rect、Rect)
二つのRectを渡すとその2つが収まるピッタリのRectを返してくれる
3つ以上の時は返ってきたRectと3つめのRectを渡すことをすれば
いくつでもいい
 
    Private Function GetUnion(ex As ObservableCollectionExImage) As Rect
        Dim r As Rect = GetRect(ex(0))
        'すべてのRectangleのUnionを取得
        For i As Integer = 1 To ex.Count - 1
            r = Rect.Union(r, GetRect(ex(i)))
        Next
        Return r
    End Function
 
これでOK
得られたRectのサイズをCanvasに指定するは
        Dim r As Rect = GetUnion(CollectionExImage)
        canvas1.Width = r.Width
        canvas1.Height = r.Height
これでOK
 
イメージ 16
水色画像をウィンドウより外側の右下に移動させたところ
スクロールバーが表示された
 
でも左か上方向に移動させた場合
イメージ 17
スクロールバーが表示されないし
 
ウィンドウより小さな範囲の時
イメージ 19
この場合も左上に寄せて
 
イメージ 20
このように表示させたい
赤枠になる範囲の左上をCanvasの左上にピッタリ合わせれば良さそう
それぞれの左上がどれだけ離れているかは
さっき取得したRectに記録されているのでそれを使って
全部の画像を移動させる
 
さっきのこの状態
イメージ 21
この時に取得したRectの中を見てみると
 
イメージ 18
-54,-42,154,142
-54,-42が位置、154,142が幅と高さのサイズ
位置がマイナスになっていて左上が表示されていない
0にすればピッタリになるので
すべての画像の位置(x,y)にx+54、y+42すればいいことになる
 
    '位置調整
    Public Sub AjustLocation()
        Dim r As Rect = GetUnion(CollectionExImage)
        canvas1.Width = r.Width
        canvas1.Height = r.Height
        If r.X <> 0 OrElse r.Y <> 0 Then
            For Each ex As ExImage In CollectionExImage
                SetLeft(ex, GetLeft(ex) - r.X)
                SetTop(ex, GetTop(ex) - r.Y)

            Next
        End If
    End Sub
ズレていた場合だけ処理すればいいので
If r.X <> 0 OrElse r.Y <> 0 Then
位置が0以外の時だけ処理
 
この処理をいつするか
一番かっこいいのは画像をマウスドラッグで移動している最中なんだけど
今の方法だとウィンドウより外側に移動させた瞬間に
ものすごい勢いでCanvasが大きくなり続けてしまうので
マウスボタンを離した時で妥協…
 
ExImage(画像)クラスでマウスの左ボタンを離した時にしたいから
OverridesしたOnPreviewMouseLeftButtonUpに書きたい
でも位置調整のメソッドはMainWindowの方に書いてあって直接呼べないので
これを参照できるように
ExImageクラスに
    Private Main As MainWindow

    Public Sub New(o As MainWindow)
        Main = o
    End Sub
こう書いた(少し使い方が間違っているかも、よくわかっていない)
クラス作成時にMainWindowを取得してMainって名前にした変数に入れておいて
このMainから位置調整のメソッドAjustLocationを呼ぶようにした
なのでMainWindowのほうでExImageを作成するときは
        Dim ex As New ExImage()
こうだったのを書きなおして
        Dim ex As New ExImage(Me)
こうなった
引数にMe(自分自身のMainWindow)を渡す
 
これでExImageクラスからMainWindowのPublicの付いた
メソッドを呼び出せるようになったので
    Protected Overrides Sub OnPreviewMouseLeftButtonUp(e As MouseButtonEventArgs)
        MyBase.OnPreviewMouseLeftButtonUp(e)
        Main.AjustLocation()
    End Sub
これで動いている
 
次回は画像ファイルとして保存

gogowaten.hatenablog.com

 
WPFVBでアプリ作る準備その6、Canvas内に表示している複数画像を1枚の画像にして保存 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13921249.html