午後わてんのブログ

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

WPFとVBでアプリ作る準備その7、指定した間隔ごとにコントロールをマウスドラッグ移動

 
 
コントロールをマウスドラッグ移動のとき指定ピクセル数おきに移動させる
見えないマス目(グリッド)に合わせる移動
 
イメージ 1
グリッドの数値を変化させて
100x100の画像を移動しているところ
前回は昨日の
続き
前回の記事
WPFVBでアプリ作る準備その6、Canvas内に表示している複数画像を1枚の画像にして保存 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13921249.html
の続きになる
 
デザイン画面とXAML

f:id:gogowaten:20191025105020p:plain

赤いところが前回から追加したところ
17行目
TextBlock追加、名前は"tbLocate"にした
選択画像の位置を表示する用
 
23行目
Slider追加、名前はgridSdrにした
このスライダーの値を移動間隔にする、値が10なら10ピクセルごとに移動
指定数値は1から64、変化させる最小値は1にしたいのでメモリ間隔の
TickFrequencyは初期値の1そのままでIsSnapToTickFrequencyをTrueに指定
IsManipulationEnabledも有効にしたほうが良さそうだったのでTrue
 
24行目
TextBlock追加、名前は"masuTbk"にした
スライダーの値が変化した時に自動で更新して表示したいので
Textプロパティはバインディングってのを使って
Text="{Binding ElementName=gridSdr, Path=Value}"
これでスライダーを動かしたら連動して値を表示してくれる
このバインディングってのはWPFの特徴みたいなんだけど
まだよくわかってない
 
ExImageクラス
f:id:gogowaten:20191025105030p:plain
前回から変化なし
 
MainWindowクラス

f:id:gogowaten:20191025105043p:plain

 

12から65行目、98、135、153行目が今回書き加えたところ
Imports System.IO
Imports System.Windows.Controls.Primitives
Imports System.Windows.Controls.Panel
Imports System.Windows.Controls.Canvas


Class MainWindow
    Private WithEvents FocusExImage As ExImage '選択中の画像(ExImage)
    'すべてのExImageを入れておくリストコレクション
    Private CollectionExImage As New ObservableCollectionExImage

    'Locate表示のtextBlockの更新
    Private Sub DisplayUpdateLocate(ex As ExImage)
        tbLocate.Text = "Locate = " & GetLeft(ex) & "," & GetTop(ex)
    End Sub

    'ExImageを左上のグリッドに移動
    Private Sub AjustGrid(ex As ExImage)
        Dim p As Point = GetRect(ex).Location '位置取得
        Dim g As Integer = gridSdr.Value '指定グリッド数値取得
        Dim xm As Integer = p.X Mod g '横位置をグリッドで割った余り
        Dim ym As Integer = p.Y Mod g '縦位置を~

        If xm <> 0 Then '0以外なら
            SetLeft(ex, GetLeft(ex) - xm) '最寄りの左のグリッドに移動
        End If
        If ym <> 0 Then
            SetTop(ex, GetTop(ex) - ym) '最寄りの上のグリッドに移動
        End If

        Call DisplayUpdateLocate(ex)
    End Sub

    'マウスドラッグ移動、グリッドに合わせた移動
    Private Sub ExImage_DragMove(sender As Object, e As DragDeltaEventArgs)
        Dim g As Integer = gridSdr.Value
        Dim ex As ExImage = DirectCast(sender, ExImage)
        Dim xMove As Integer = e.HorizontalChange
        Dim yMove As Integer = e.VerticalChange
        'この方法だとマウスを早く動かした時に動きがぎこちなくなる
        'If xMove Mod g = 0 Then
        '    SetLeft(sender, xMove + GetLeft(sender))
        'End If
        'If yMove Mod g = 0 Then
        '    SetTop(sender, yMove + GetTop(sender))
        'End If

        'これならOK、Pixtack紫陽花と同じ方式
        Dim xIma As Integer = GetLeft(ex) + xMove 'ExImageの横位置 + マウスの横移動距離
        Dim yIma As Integer = GetTop(ex) + yMove '縦位置

        Dim xMod As Integer = xIma Mod g '横位置をグリッド数値で割った余り
        Dim yMod As Integer = yIma Mod g '縦位置

        '移動先指定
        SetLeft(ex, xIma - xMod) '横位置 - 余り
        SetTop(ex, yIma - yMod)

        Call DisplayUpdateLocate(ex)
    End Sub

    'ExImageを作成して追加した直後に動かす
    Private Sub ExImage_Loaded(sender As Object, e As RoutedEventArgs)
        Call AjustGrid(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)
        Dim bmp As BitmapImage = GetBitmapImage(filesPath)
        With ex
            .Source = bmp ' GetBitmapImage(filesPath)
            '.Width = bmp.PixelWidth '100ピクセルの画像をSourceに指定した時
            '.Height = bmp.PixelHeight 'ExImageのサイズ指定しないとExImageは100.0139になる
        End With
        SetLeft(ex, locate.X) '表示する位置は必須、指定しないとDragdeltaイベントで移動量が取得できない
        SetTop(ex, locate.Y)  '必須
        AddHandler ex.ExDragDelta, AddressOf ExImage_DragMove 'これはマウスドラッグ用
        AddHandler ex.MouseDown, AddressOf ExImage_MouseDown '画像をクリックした時に動かすメソッド
        AddHandler ex.Loaded, AddressOf ExImage_Loaded
    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)
        Call AjustGrid(sender)
        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
            Call AjustGrid(FocusExImage)
        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



    '画像ファイルとして保存

    '        キャンバスに描いた絵を画像ファイルとして保存する | HIRO's.NET Blog
    'http://blog.hiros-dot.net/?page_id=3802
    'Daizen Ikehara : [WPF] XamQRCodeBarcode を画像として保存 [Tips]
    'http://blogs.jp.infragistics.com/blogs/dikehara/archive/2014/02/12/wpf-xamqrcodebarcode-tips.aspx
    '    RenderTargetBitmap tips - Jaime Rodriguez - Site Home - MSDN Blogs
    'http://blogs.msdn.com/b/jaimer/archive/2009/07/03/rendertargetbitmap-tips.aspx

    Private Sub SaveAllImage()
        If CollectionExImage.Count = 0 Then Return '画像がなければ何もしない

        'ダイアログ設定
        Dim dialogSave As New Microsoft.Win32.SaveFileDialog
        With dialogSave
            .Filter = "*.png|*.png|*.jpg|*.jpg;*.jpeg|*.bmp|*.bmp|*.gif|*.gif|*.tiff|*.tiff"
            .AddExtension = True
        End With

        'ダイアログ表示
        If dialogSave.ShowDialog Then
            '保存画像サイズ取得
            Dim canvasRect As Rect = GetUnion(CollectionExImage)

            '描画先を作成
            Dim dv As New DrawingVisual
            Using dc As DrawingContext = dv.RenderOpen
                Dim vb As New VisualBrush(canvas1) 'Canvas内に表示されているもの自体を使ってVisualBrush作成
                dc.DrawRectangle(vb, Nothing, canvasRect) '四角形にブラシで塗り
            End Using

            '描画
            Dim rtb As New RenderTargetBitmap(canvasRect.Width, canvasRect.Height, 96, 96, PixelFormats.Pbgra32)
            rtb.Render(dv)

            '画像エンコーダ選択
            Dim enc As BitmapEncoder = Nothing
            Select Case dialogSave.FilterIndex
                Case 1
                    enc = New PngBitmapEncoder
                Case 2
                    Dim je As New JpegBitmapEncoder
                    je.QualityLevel = 97 '1-100 初期値は75
                    enc = je
                Case 3
                    enc = New BmpBitmapEncoder
                Case 4
                    enc = New GifBitmapEncoder
                Case 5
                    enc = New TiffBitmapEncoder
            End Select

            'エンコーダに画像フレームを渡す
            Dim bf As BitmapFrame = BitmapFrame.Create(rtb)
            enc.Frames.Add(bf)

            'ファイルとして保存
            Using fs As New FileStream(dialogSave.FileName, FileMode.Create)
                enc.Save(fs)
            End Using
        End If
    End Sub

    '画像ファイルとして保存
    Private Sub save_Click(sender As Object, e As RoutedEventArgs) Handles save.Click
        Call SaveAllImage()
    End Sub
    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
DisplayUpdateLocate
イメージ 4
デザイン画面で選択画像(ExImage)の位置表示をしているtvLocateの
表示を更新するだけのメソッド

使っているところは
画像をグリッドに合わせる
    Private Sub AjustGrid
画像の移動中
    Private Sub ExImage_DragMove
このふたつ


AjustGrid
イメージ 6
選択画像(ExImage)の位置を最寄りのグリッド(マス目)に移動させるメソッド
 
実行するタイミングはマウスでクリックした直後の
    Private Sub ExImage_MouseDown(sender As Object, e As RoutedEventArgs)
と、画像を移動し終わった時に位置合わせのメソッドの
    Public Sub AjustLocation()
この2つ
 
移動先の位置は指定された数値の倍数になればいいんだから
元の位置を割った余りを求めて
元の位置から引けばいい
 
指定された数値(マス目間隔)が10で画像の位置がx=11,y=29の時に実行すると
画像の位置はx=10、y=20になるようにしている(左上に寄せる)
p = 11,29
g = 10
xm = 11 Mod 10 = 1 (11÷10の余りは1)
ym = 29 Mod 29 = 9 (29÷10の余りは9)
xm = 1
ym = 9
x = 11 - 1 (p.x - mx)
y = 29 - 9 (p.y - my)
x = 10
y = 20
でいいんだけど今見なおしたら
最初に位置取得しているのに25,28行目でまた位置取得している
っていうムダな計算しているな
 
 
ExImage_DragMove

f:id:gogowaten:20191025105113p:plain

マウスドラッグ移動の時にグリッドに合わせる
さっきのAjustGridメソッドで移動前の位置はグリッドに合っているはずなので
マウスの移動距離がグリッドぶん動いた時にその位置に移動させればいいってことで
マウスの移動距離÷マス目間隔の余り = 0になった時に移動
ってのが最初の方法だったけど動きがいまいち
どうやら0になった瞬間ってのがよくなさそうだった

なので常に移動させるつもりで、もし移動先がグリッドとズレていたら動かさない
っていう方法にしたらうまく行ったというかPixtack紫陽花と同じ方法になった


ExImage_Loaded

f:id:gogowaten:20191025105124p:plain

画像(ExImage)のLoadedイベントの時にAjustGridを実行する
画像を追加した時にグリッドに合わせるようにした方がいいかなって
書いたけど要らないかな

イメージ 9
イメージ 10
イメージ 11
複数の画像を任意の間隔で並べて1枚の画像にしたくて
 

f:id:gogowaten:20191025105136p:plain

Pixtack紫陽花を作ったんだけどWPFでもやっとそれっぽくなってきた
 
 
予定しているテスト
  • 指定した画像の表示を消す(削除)
  • 指定した色を透明にする
  • レイアウト
 
完了したテスト
  • 画像ファイルドロップで画像表示
  • 画像をマウスドラッグで移動
  • 表示画像のレイヤー間の移動(入れ替え)ZOrder指定
  • 表示画像すべてを画像ファイルとして保存
  • ドラッグ移動時に指定ピクセル数ごとに移動 ←New
 
 
 続きは翌日