午後わてんのブログ

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

WPFとVBでアプリ作る準備その1、マウスドラッグでコントロールの移動

 
 
イメージ 1
 
WPFではアプリのウィンドウにボタンとかのコントロールを複数表示させるには
ウィンドウの中にパネル系のコントロールを置いて
その中に色々ボタンとかのコントロールを置いていくみたい
<Window>
    <パネル系のコントロール>
        <いろいろ>
        </いろいろ>
        <いろいろ>
        </いろいろ>
    </パネル系のコントロール>
</Window>
 
 
パネル系のコントロールもいろいろある
アプリを新規作成した時に最初から設置されているのがGridなので
Gridが代表格なのかも、その他でよく見かけるのがStackPanel
ツールボックスのコモンコントロールにもこのふたつが入っている
イメージ 7
Canvas、TabControl、WrapPanelとか他にもあるみたい
癖がなくて使いやすそうな感じなのが
GridとCanvasのふたつ
「マウスドラッグでコントロールを移動」とかで
ググってみるとCanvasを使っているケースが多いけど
両方試してみた
 
このふたつは特に指定しないと置いた場所全体に広がる
Windowに置けばWindow全体に広がる
 
コントロールのドラッグ移動で必要なのが位置の取得と指定なんだけど
GridとCanvasではこのコントロールの位置の決め方が違う
Gridの場合

f:id:gogowaten:20191024155614p:plain

GridにButtonを設置したところ
Marginプロパティで場所指定する
 
左寄せと上寄せをしないと変な位置になるので
HorizontalAlignment="Left" VerticalAlignment="Top"を指定している
Marginの4つの数字は順番に左、上、右、下の値になる
左寄せと上寄せを指定するとMarginの右と下の値は無視される
 
Canvasの場合

f:id:gogowaten:20191024155626p:plain

デザイン画面でCanvasの場合はCanvas.Leftとかで指定
こうしてみると位置の決め方はGridよりCanvasのほうがラク

 
 
マウスドラッグでコントロールを移動させる処理の流れ
 
マウスダウンイベントで
ドラッグ移動中フラグにする
最初のマウスの位置を記録しておく
 
マウスムーブイベントで
ドラッグ移動中フラグなら
動かした今の位置から最初の位置を引き算して移動距離を測る
移動距離と動かしているコントロールの今の位置を足し算したのが
移動させる位置なのでこれを指定する
 
マウスアップイベントで
ドラッグ移動中フラグを解除
 
 
動かすコントロールはRectangleにしてみた
 
Grid編

f:id:gogowaten:20191024155637p:plain

最初からあるGridにgrid1と言う名前を付けただけ
 
VBコード

f:id:gogowaten:20191024155650p:plain

Class MainWindow
    Private syokiX As Double 'クリックした横位置
    Private syokiY As Double 'クリックした縦位置
    Private IsDrag As Boolean 'ドラッグ中の判定用

    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        'Rectangle作成
        Dim r As New Rectangle
        Dim b As New SolidColorBrush(Color.FromArgb(128, 255, 0, 0)) '塗りつぶし用のブラシ
        With r
            .Width = 100     '横幅
            .Height = 100
            .Fill = b        'ブラシで塗りつぶし
            .Margin = New Thickness(10, 20, 0, 0) 'Gridの左、上、右、下の外枠からの距離
            .HorizontalAlignment = HorizontalAlignment.Left  '左寄せ
            .VerticalAlignment = VerticalAlignment.Top       '上寄せ
        End With
        'Rectangleをgridに追加
        grid1.Children.Add(r)

        '各マウスイベント時に動かすメソッドを指定
        AddHandler r.MouseDown, AddressOf Rectangle_MouseDown
        AddHandler r.MouseMove, AddressOf Rectangle_MouseMove
        AddHandler r.MouseUp, AddressOf Rectangle_MouseUp
    End Sub

    Private Sub Rectangle_MouseDown(sender As Rectangle, e As MouseButtonEventArgs)
        syokiX = e.GetPosition(sender).X 'クリックした位置記録
        syokiY = e.GetPosition(sender).Y
        IsDrag = True         '判定をドラッグ中にする
        sender.CaptureMouse() 'マウスカーソルがRectangleから離れないように掴んでおく
    End Sub
    Private Sub Rectangle_MouseMove(sender As Rectangle, e As MouseEventArgs)
        If IsDrag Then 'ドラッグ中なら以下を実行
            Dim p As Point = e.GetPosition(sender) '今のマウスの位置取得
            Dim t As New Thickness
            t.Left = p.X - syokiX + sender.Margin.Left   'Rectangleの横位置
            t.Top = p.Y - syokiY + sender.Margin.Top     'Rectangleの縦位置
            sender.Margin = t                            'Rectangleの位置指定

            'マウスの移動量 = マウスの今の位置 - マウスの最初の位置
            '                       p.X        -    syokiX

            'Rectangleの移動後位置 = マウスの移動量 + Rectangle移動前の位置
            '                        マウスの移動量 + sender.Margin.Left

            '一行にまとめると
            'Rectangleの移動後位置 = マウスの今の位置 - マウスの最初の位置 + Rectangle移動前の位置
            't.Left                =        p.X       -     syokiX         + sender.Margin.Left
        End If
    End Sub
    Private Sub Rectangle_MouseUp(sender As Rectangle, e As MouseButtonEventArgs)
        IsDrag = False               '判定をマウスドラッグ中ではないにする
        sender.ReleaseMouseCapture() 'Rectangleからマウスカーソルを開放する
    End Sub
End Class
Loadedイベントの時にRectangleを作成してgrid1に追加している。
 
位置の指定はMarginプロパティに
Thicknessっていう見慣れないのを指定している
Thicknessってのは構造体なのかな?4つの数値を入れることができる
位置の取得は
sender(動かしているコントロール).Margin.leftとかでそのまま取得できる
 
 

f:id:gogowaten:20191024155724p:plain

f:id:gogowaten:20191024155734p:plain

Class MainWindow
    Private syokiX As Double 'クリックした横位置
    Private syokiY As Double 'クリックした縦位置
    Private IsDrag As Boolean 'ドラッグ中の判定用

    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        'Rectangle作成
        Dim r As New Rectangle
        Dim b As New SolidColorBrush(Color.FromArgb(128, 255, 0, 0)) '塗りつぶし用のブラシ
        With r
            .Width = 100     '横幅
            .Height = 100
            .Fill = b        'ブラシで塗りつぶし
            .HorizontalAlignment = HorizontalAlignment.Left  '左寄せ
            .VerticalAlignment = VerticalAlignment.Top       '上寄せ
        End With
        'Rectangleをgridに追加
        canvas1.Children.Add(r)

        'Rectangleの位置指定
        Canvas.SetLeft(r, 10) '横
        Canvas.SetTop(r, 20) '縦

        '各マウスイベント時に動かすメソッドを指定
        AddHandler r.MouseDown, AddressOf Rectangle_MouseDown
        AddHandler r.MouseMove, AddressOf Rectangle_MouseMove
        AddHandler r.MouseUp, AddressOf Rectangle_MouseUp
    End Sub

    Private Sub Rectangle_MouseDown(sender As Rectangle, e As MouseButtonEventArgs)
        syokiX = e.GetPosition(sender).X 'クリックした位置記録
        syokiY = e.GetPosition(sender).Y
        IsDrag = True         '判定をドラッグ中にする
        sender.CaptureMouse() 'マウスカーソルがRectangleから離れないように掴んでおく
    End Sub
    Private Sub Rectangle_MouseMove(sender As Rectangle, e As MouseEventArgs)
        If IsDrag Then 'ドラッグ中なら以下を実行
            Dim p As Point = e.GetPosition(sender) '今のマウスの位置取得
            Canvas.SetLeft(sender, p.X - syokiX + Canvas.GetLeft(sender))
            Canvas.SetTop(sender, p.Y - syokiY + Canvas.GetTop(sender))
            'Rectangleの移動後位置 = マウスの今の位置 - マウスの最初の位置 + Rectangle移動前の位置
            '                      =        p.X       -     syokiX         + Canvas.GetLeft(sender)
        End If
    End Sub
    Private Sub Rectangle_MouseUp(sender As Rectangle, e As MouseButtonEventArgs)
        IsDrag = False               '判定をマウスドラッグ中ではないにする
        sender.ReleaseMouseCapture() 'Rectangleからマウスカーソルを開放する
    End Sub

End Class
位置の指定はCanvasクラスのSetLeftとSetTopメソッドを使っている
動かしているコントロールの今の位置を取得するときは
GetLeftとGetTopメソッド、わかりやすい
 
実行した時の画面
イメージ 6
Grid編もCanvas編も全く同じ
見た目も動きも全く同じ
グリグリ動かした時のCPUの占有率も同じくらいで8%前後
なのでわかりやすいCanvasがいいかなあと



動くのと動かないの
ここまでの方法で動かしたコントロールはRectangle
他のコントロールも動かせるのかいくつか試してみた
その時はイベントの時に使うメソッドの引数の型をRectangleからObjectに変更
Private Sub Rectangle_MouseDown(sender As Rectangle, e As MouseButtonEventArgs)
Private Sub Rectangle_MouseDown(sender As Object, e As MouseButtonEventArgs)
動かせたのはImage、TabControl、Ellipse
動かないのはButton、ComboBox
動かしたいのは画像なので画像表示に便利そうなImageが動けばいいや



Thumbコントロール
っていうのがあってマウスでのドラッグ移動に便利なイベントを持っている
その名もDragDelta
引数の(e As DragDeltaEventArgs)からマウスの移動距離が取得できる
e.HorizontalChangeで横の移動距離
e.VerticalChangeで縦の移動距離
スゴイ
Thumbコントロールなら

f:id:gogowaten:20191024155750p:plain

これだけで動く、実質2行
しかもグリグリ動かした時のCPU占有率が4%前後と低い!
だけど見た目が…
イメージ 11
こんなふうにボタンみたいな枠が付いてしまうので
 
例えば
イメージ 15
この画像をThumbの背景にして表示すると
イメージ 16
やっぱり余計な枠が付いている
なので枠の付かない
ImageコントロールにDragDeltaイベントがあればいいんだけどなあ
 
 
イメージ 12
ThumbだとDragDeltaイベントが候補に出てくるけど
 
イメージ 13
Imageには出てこないし
 
イメージ 14
手動で書いてもエラーになるw
ThumbのBackgroundに半透明の画像を指定しても透過処理にならない
イメージ 21
二つのThumbコントロールは半透明のブラシを使って
塗りつぶしているんだけど透過処理になってない
ImageコントロールはSourceに半透明の画像を指定するだけで
透過処理になっていたんだけどなあ
Backgroundではならないみたい
ImageコントロールにDragDeltaイベントを付ける方法が
わかるまではCanvasに普通のImageコントロールかなあ
 
 
ここまでとこの前の

gogowaten.hatenablog.com

WPF、アプリのウィンドウに画像ファイルドロップで画像表示テスト ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ

http://blogs.yahoo.co.jp/gogowaten/13894909.html

これを組み合わせて作ったのが↓
イメージ 17
 
 

f:id:gogowaten:20191024155809p:plain

画像ファイルドロップで表示した画像を
マウスドラッグで移動できる
CanvasにImageコントロールの組み合わせ
それにしても半透明の透過処理を自動でしてくれるWPFはスゴイ
 
このテストアプリの中身
デザイン画面

f:id:gogowaten:20191024155822p:plain

VBコード

f:id:gogowaten:20191024155833p:plain

コピペして作ったからコメントアウトでの説明がRectangleのままになっているw
例外処理してないから画像以外のファイルをドロップするとエラーになる
Imports System.IO

Class MainWindow
    Private syokiX As Double 'クリックした横位置
    Private syokiY As Double 'クリックした縦位置
    Private IsDrag As Boolean 'ドラッグ中の判定用

    Private Sub MainWindow_Drop(sender As Object, e As DragEventArgs) Handles Me.Drop
        'ドロップされたファイルのパスを取得
        Dim filesPath() As String = e.Data.GetData(DataFormats.FileDrop)
        'ファイルパスの画像を割り当てたImageコントロールをGridに表示
        Dim locate As New Point(0, 0)
        For i As Integer = 0 To filesPath.Length - 1
            Call AddImage(filesPath(i), locate) '画像表示
            locate.Offset(30, 30)
        Next
    End Sub

    'ファイルパスからBitmapImage(画像)を作成して返す
    '速い?
    'どうやらBitmapImageはXAMLで表示する時用のBitmapみたい
    '    BitmapImage クラス(System.Windows.Media.Imaging)
    'https://msdn.microsoft.com/ja-JP/library/system.windows.media.imaging.bitmapimage(v=vs.110).aspx

    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

    ''ファイルパスからBitmapSource(画像)を作成して返す
    ''遅い
    'Private Function GetBitmapSource(filePath As String) As BitmapSource
    '    Dim bmp As BitmapSource
    '    Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read)
    '        Dim bd As BitmapDecoder = BitmapDecoder.Create(
    '            fs, BitmapCreateOptions.None, BitmapCacheOption.OnLoad)
    '        bmp = New WriteableBitmap(bd.Frames(0))
    '        bmp.Freeze()
    '    End Using
    '    Return bmp
    'End Function

    'ImageControlを作成してGridに追加
    Private Sub AddImage(filesPath As String, locate As Point)
        'ファイルパスからBitmapImage(画像)を取得
        Dim bmp As BitmapImage = GetBitmapImage(filesPath)
        'Dim bmp As BitmapSource = GetBitmapSource(filesPath)
        'ImageControlを作成して画像やサイズを指定
        Dim img As New Image
        With img
            .Source = bmp
            .Width = bmp.PixelWidth
            .Height = bmp.PixelHeight
        End With
        'GridにImageを追加(表示)する
        canvas1.Children.Add(img)
        Canvas.SetLeft(img, locate.X)
        Canvas.SetTop(img, locate.Y)

        '各マウスイベント時に動かすメソッドを指定
        AddHandler img.MouseDown, AddressOf Image_MouseDown
        AddHandler img.MouseMove, AddressOf Image_MouseMove
        AddHandler img.MouseUp, AddressOf Image_MouseUp
    End Sub




    Private Sub Image_MouseDown(sender As Image, e As MouseButtonEventArgs)
        syokiX = e.GetPosition(sender).X 'クリックした位置記録
        syokiY = e.GetPosition(sender).Y
        IsDrag = True         '判定をドラッグ中にする
        sender.CaptureMouse() 'マウスカーソルがRectangleから離れないように掴んでおく
    End Sub
    Private Sub Image_MouseMove(sender As Image, e As MouseEventArgs)
        If IsDrag Then 'ドラッグ中なら以下を実行
            Dim p As Point = e.GetPosition(sender) '今のマウスの位置取得
            Canvas.SetLeft(sender, p.X - syokiX + Canvas.GetLeft(sender))
            Canvas.SetTop(sender, p.Y - syokiY + Canvas.GetTop(sender))
            'Rectangleの移動後位置 = マウスの今の位置 - マウスの最初の位置 + Rectangle移動前の位置
            '                      =        p.X       -     syokiX         + Canvas.GetLeft(sender)
        End If
    End Sub
    Private Sub Image_MouseUp(sender As Image, e As MouseButtonEventArgs)
        IsDrag = False               '判定をマウスドラッグ中ではないにする
        sender.ReleaseMouseCapture() 'Rectangleからマウスカーソルを開放する
    End Sub
End Class

予定しているテスト
  • 表示画像すべてを画像ファイルとして保存
  • ドラッグ移動時に指定ピクセルごとに移動
  • 表示画像のレイヤー間の移動(入れ替え)ZOrder指定
  • 指定した色を透明にする
  • レイアウト
上の3つは必要な機能


 
WPFVBでアプリ作る準備その2、ControlTemplateの中のControlを取得する ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13906217.html

WPFVBでアプリ作る準備その3、ImageコントロールにDragDeltaイベントがほしくて擬似DragDelta ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13911101.html
2016年5月21日追記

gogowaten.hatenablog.com

ControlTemplateを使って枠のないThumbができた

 
WPFVB.NET、ControlTemplateをコードで作成 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14156250.html
2016年5月22日追記

gogowaten.hatenablog.com 

WPFVB.NET、ControlTemplateを使ったThumbを回転表示する時に回転させるのはどれがいいのか ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14157487.html
 
 
 
 
5年後はC#

 

gogowaten.hatenablog.com

5年経っても同じことしてる