午後わてんのブログ

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

WPFとVBでアプリ作る準備その4、コントロールを重ねた時の上下移動(ZOrder)はPanel.SetZIndex

前回は昨日
の続き
 
 
 
コントロールを上下に重ねた時の位置関係を指定する
WPFではZIndexプロパティで指定する
 
テスト結果
イメージ 25
選択画像のZ位置をボタンで変更
 
ZIndex(上下の位置)を指定しない場合
イメージ 1
ボタンを3つ重なるように配置した状態
ZIndex(上下の位置)を指定しないと最初に配置したものが一番下になって
後に配置したものが上に重なっていく
 

f:id:gogowaten:20191025101925p:plain

指定しない時のZIndexの値は0になっている
ここからbutton1のZIndexを1にすると

f:id:gogowaten:20191025101936p:plain

button1が一番上になる
 
拡大
イメージ 3
ZIndexを文字で指定するときはPanelを付けて
Panel.ZIndex="1"とかになるみたい。
 
 
大きい数値が指定されたものほど上に表示される
マイナスの数値も指定できる
同じ数値の者同士なら後に設置されたものが上になる
 
button1をクリックしたらZIndexを1に変更するをVBのコードで書く場合
デザイン画面

f:id:gogowaten:20191025101948p:plain

button3の位置を少し変えて、button1のZIndexを指定なしにした状態。
button1をダブルクリックすると
 

f:id:gogowaten:20191025101958p:plain

この画面になるので
 

f:id:gogowaten:20191025102021p:plain

        Panel.SetZIndex(sender, 1)
これでbutton1のZIndexが1になる
senderはクリックイベントを起こしたbutton1自身
 
実行して
イメージ 8
button1をクリックすると
 
イメージ 9
一番上になった
 
ZIndexを指定するときは
Panel.SetZIndex
ZIndexを取得するには
Panel.GetZIndex
 
Windowフォームアプリだと最前面か最背面の指定しかできなかったので
ひとつ上か下に移動したい時でも全コントロール数の半分を移動させる必要があって
それに比べるとWPFのSetZIndexはかなり便利
エクセルVBAでも最前面、最背面、ひとつ上、ひとつ下へ移動させるっていう便利なのがあるんだよねえ
 
上下の表示位置の指定はだいたいわかったので
ZIndexを指定する画像はどれなのか判別できるように
選択中の画像を表示する見本画像を用意
選択中の画像をひとつ上、ひとつ下へ移動させるボタン
選択中の画像のZIndexを表示するTextBlock
などを用意して
 
デザイン画面はこうしてみた

f:id:gogowaten:20191025102052p:plain

XAMLの部分もテキストでここに表示したいけど
これをそのまま貼り付けて記事を投稿するとエラーになってしまうことが多い
<>がいっぱいあるのが良くないのかなあ
投稿はできたようにみせかけて投稿した記事を見るをクリックすると
記事が見つかりませんというエラー
なので実質全部書き直しになるし確認ボタンでのプレビューでは問題ないから
怖くてできないので画像だけ
 
 
それぞれの画像のZIndexは0から順番につけて同じ数値がつかないようにしたい
なので画像が全部でいくつ表示されているのかとかまとめて取得しておきたいので
Pixtack紫陽花の時と同じCollectionのList(Of Image)を使ってみたんだけど
ググっていたらいいのがあった
ObservableCollection
これ
Itemの移動させるMoveメソッドが今回の目的にピッタリ
中に入れたItemにはIndexが0から順番についていくので
この番号をZIndexに合わせればいいなと
Moveメソッドで移動させたらIndexも変化するので
そのIndex番号をSetZIndexで使えば一石二鳥な感じ
なのでこのObservableCollectionを継承したクラスを作成
作成場所は前回作ったExImageクラスと同じ場所にしてみてこうなった
 
画像
 
 
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)

    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

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
 
32行目から最後までが今回書き加えたところ

f:id:gogowaten:20191025102109p:plain

Inherits ObjectModel.ObservableCollection(Of ExImage)
ObservableCollectionを継承して、ExImageクラスを入れることにした
 
Itemを移動した時についでにZIndexを指定したいので
MoveItemメソッドをOverridesしてそこに処理を書く
イメージ 12
Overrides スペースキーでOverridesできる一覧が出るので
MoveItemを選択してタブキー押すと
 
イメージ 13
これだけ自動で入力される、便利だなあ
 
動かしている時の中の様子
イメージ 15
OldIndexが元の番号でNewIndexが移動先の番号
画像が3つ入っている時に0番を1番に移動しようとしている時の様子
これはそのまま問題なく処理が進む
 
 
 
問題になるのは移動先の番号が範囲を超えてしまう時
-1以下や3以上だと場所がないのでエラーになる
 
イメージ 16
これは指定された移動先が-1になっている、Indexは0,1,2のどれかで
-1番はないからエラーになるので対処
 
 
        MyBase.MoveItem(oldIndex, newIndex)
この部分が実際のItem移動の処理の部分だと思うから、この直前で
 
        If newIndex >= Count OrElse newIndex < 0 Then Return
範囲外だったら処理前に終了(Return)
 
 
        SetZIndex(Item(oldIndex), oldIndex)
        SetZIndex(Item(newIndex), newIndex)
移動が終わったらSetZIndexで画像(ExImage)の表示位置の移動
移動させるってのは隣と入れ替わるってことだから
自分自身と隣の画像の両方のZIndexを指定する
 
 
 
MainWindowのVBコード

f:id:gogowaten:20191025102128p:plain

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
        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
        If FocusExImage Is Nothing Then Return
        Dim z As Integer = CollectionExImage.IndexOf(FocusExImage)
        CollectionExImage.Move(z, z + 1)
        Call kousin()
    End Sub

    '1つ下に移動
    Private Sub sage_Click(sender As Object, e As RoutedEventArgs) Handles sage.Click
        If FocusExImage Is Nothing Then Return
        Dim z As Integer = CollectionExImage.IndexOf(FocusExImage)
        CollectionExImage.Move(z, z - 1)
        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

End Class
主に書き加えたのは58行目
その他書き加えたところは
イメージ 22
3行目
これを書いておくと今回多用する
Panel.SetZIndexやPanel.GetZIndexを
SetZIndexやGetZIndexと書くだけで済むようになる
けど今見なおしたらあんまり書いてなかったw
 

f:id:gogowaten:20191025102157p:plain

8行目
クリックした画像(ExImage)を記録しておく用
このExImageを上下移動させる、見本表示にも使う
9行目
全画像を入れておくリストコレクションを作成
ObservableCollectionクラスを継承して作ったObservableCollectionExImageを
作成、これに全部の画像(ExImage)を入れる
 
 
イメージ 18
34行目で作成したExImageを35行目でリストコレクションの
ObservableCollectionExImageに入れる
 
36行目、作成したExImageのZIndexを指定
指定する数値はリストコレクションに入っているItemの数-1
 
42行目:ExImageのMouseDownイベント発生時に動かすメソッド
ExImage_MouseDownを指定
このメソッドは77行目
イメージ 19
78行目
クリックした画像をFocusExImage変数に入れる
デザイン画面で作っておいた見本用のmihonのSourceにクリックした画像を
指定して見本画像を表示する
80行目
デザイン画面で作っておいたTextBlockの表示更新
 
イメージ 20
TextBlockのtbZIndexに選択中の画像のZIndexの値を表示する
 
選択画像を上下させる部分
イメージ 21
63行目、選択画像がなければ何もしない
64:選択画像のIndex番号を取得
65:リストコレクションのMoveメソッドでItemを移動
第1引数が移動させたいIndex、第2引数が移動先のIndex番号
ひとつ上に移動だから1を足している
66:TextBlockの表示の更新は58行目へ
 
 
イメージ 23
画像ファイル4つをドロップして表示して
一番下にある緑の画像を選択したところ
一番下なのでZIndexは0
ここからageボタンを押すと

イメージ 24
ひとつ上に移動して黄色画像と入れ替わる
ZIndexも1になっている
 
予定しているテスト、上の3つは必要な機能
  • 表示画像すべてを画像ファイルとして保存
  • ドラッグ移動時に指定ピクセルごとに移動
  • 指定した画像の表示を消す(削除)
  • 指定した色を透明にする
  • レイアウト
 
完了したテスト
  • 画像ファイルドロップで画像表示
  • 画像をマウスドラッグで移動
  • 表示画像のレイヤー間の移動(入れ替え)ZOrder指定←New
 
参照したところは
汎用ジェネリックコレクション その2 ObservableCollection/ReadOnlyObservableCollection (System.Collections.ObjectModel) - Programming/.NET Framework/コレクション - 総武ソフトウェア推進所
http://smdn.jp/programming/netfx/collections/3_objectmodel_2_observablecollection/
今回のテストはこことObservableCollectionのおかげでかなり満足行くできになった
Pixtack紫陽花のときはFor~Nextでぐるぐる回していたからCPUの占有率も上がっていただろうし、コードの量も2倍以上はあったなあ
 
 
 
今回の記事の続き

gogowaten.hatenablog.com

 
WPFVBでアプリ作る準備その5、スクロールバーの表示、回転とかの変形後のコントロールのサイズ取得 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13919066.html