午後わてんのブログ

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

WPFとVB.NET、エクセルのグループ化とグループ化解除を真似したい3

 
以前とは別の方法でグループ化と解除のテスト

wpf_46.gif



デザイン画面とXAML

f:id:gogowaten:20191030121229p:plain

VBコード
 
 
 

f:id:gogowaten:20191030121242p:plain

Imports System.Windows.Controls.Primitives


Class MainWindow
    Private tList2 As New List(Of ExThumb2)
    Private Const grid As Integer = 20 'グリッドの大きさ


    '選択中のExThumb
    Private Property _FocusThumb As ExThumb2
    Private Property FocusThumb As ExThumb2 '選択中のThumb
        Get
            Return _FocusThumb
        End Get
        Set(value As ExThumb2)
            _FocusThumb = value
            tbk1.Text = "FocusThumb = " & value.Name
            If value.GroupTop IsNot Nothing Then
                tbk2.Text = "FocusGroup = " & value.GroupTop.aName
            Else
                tbk2.Text = "FocusGroup = Nothing"
            End If
        End Set
    End Property


    'ExThumbの座標セット
    Private Sub SetLocate(obj As Object, p As Point)
        Canvas.SetLeft(obj, p.X)
        Canvas.SetTop(obj, p.Y)
    End Sub
    'ExThumbの座標ゲット
    Private Function GetLocate(obf As FrameworkElement) As Point
        Return New Point(Canvas.GetLeft(obf), Canvas.GetTop(obf))
    End Function

    'ExThumbのマウスドラッグイベント用
    Private Sub ThumbDragDelta(sender As Object, e As DragDeltaEventArgs)
        Dim t As ExThumb2 = DirectCast(sender, ExThumb2)
        Dim x As Double = e.HorizontalChange
        Dim y As Double = e.VerticalChange
        x -= x Mod grid
        y -= y Mod grid

        Dim np As New Point(x, y)

        'グループ用
        If t.GroupTop IsNot Nothing Then
            For Each tt As ExThumb2 In t.GroupTop.AllItems
                Call SetLocate(tt, GetLocate(tt) + np)
            Next
        Else
            Call SetLocate(t, GetLocate(t) + np)
        End If
    End Sub

    'ExThumbのマウスクリックイベント用
    Private Sub FocusThumb_PreviewMouseDown(sender As Object, e As MouseButtonEventArgs)
        'FocusThumbの切り替え
        Dim t As ExThumb2 = DirectCast(sender, ExThumb2)
        FocusThumb = t
    End Sub






    Private Sub AddExThumb2(count As Integer)
        '10個ExThumb2作成
        For i As Integer = 0 To count
            Dim t As New ExThumb2(i, New Size(grid * 2, grid * 2))

            SetLocate(t, New Point(grid * i, grid * i))
            canvas1.Children.Add(t)
            tList2.Add(t)
            AddHandler t.DragDelta, AddressOf ThumbDragDelta
            AddHandler t.PreviewMouseDown, AddressOf FocusThumb_PreviewMouseDown
            FocusThumb = t
        Next
    End Sub

    'tの中のGroup取得、重複除く、Nothing除く
    Private Function GetAllGroup2(tl As List(Of ExThumb2)) As List(Of Group2)
        Dim gl As New List(Of Group2)
        For Each t As ExThumb2 In tl
            If t.GroupTop IsNot Nothing Then
                gl.Add(t.GroupTop)
            End If
        Next
        gl = gl.Distinct.ToList
        Return gl

    End Function

    'Groupに属していない単体のtを返す
    Private Function GetSimpleExThumb(tl As List(Of ExThumb2)) As List(Of ExThumb2)
        Dim nl As New List(Of ExThumb2)
        For Each t As ExThumb2 In tl
            If t.GroupTop Is Nothing Then
                nl.Add(t)
            End If
        Next
        Return nl
    End Function


    'グループ化
    Private Sub AddGroup(tl As List(Of ExThumb2), i As Integer)
        '渡されたThumbをグループ化する
        'トップグループが1つの場合は統合
        '0か2以上ならグループを新規作成してそれに全部入れる
        If tl.Count <= 1 Then Return
        Dim gl As List(Of Group2) = GetAllGroup2(tl) 'Groupのカウント用
        Dim st As List(Of ExThumb2) = GetSimpleExThumb(tl) 'Groupに属していないthumb
        Dim g As Group2
        If gl.Count = 1 Then
            '統合の場合
            g = gl(0)
            g.Items.AddRange(st) 'Groupに属していないtを追加
            g.AllItems.AddRange(st) '全部取得用リスト
            For Each t As ExThumb2 In st
                t.GroupTop = g
            Next
        Else
            '新規作成の場合
            g = New Group2(i)
            g.Groups = gl
            g.Items.AddRange(st) 'Groupに属していないthumbを追加
            '全thumb取得用リスト
            Dim allt As New List(Of ExThumb2)
            allt.AddRange(st)
            For Each gg As Group2 In gl 'リスト作成
                allt.AddRange(gg.AllItems)
            Next
            allt = allt.Distinct.ToList '重複除去
            g.AllItems = allt
            For Each t As ExThumb2 In tl
            For Each t As ExThumb2 In allt
                t.GroupStack.Push(t.GroupTop) 'グループ階層記録用
                t.GroupTop = g
            Next
        End If

    End Sub


    'グループ化解除
    Private Sub ungroup2_1(gg As Group2)
        If gg Is Nothing Then Return

        'Groupの底上げ
        For Each t As ExThumb2 In gg.AllItems
            If t.GroupStack.Count = 0 Then
                t.GroupTop = Nothing
            Else
                Dim g As Group2 = t.GroupStack.Pop
                t.GroupTop = g
            End If
        Next

    End Sub


    'WPF: XAML, C# で TextBlock などの要素内の文字列を改行させる « をぶろぐ
    'http://tetsuwo.tumblr.com/post/59191241888/wpf-xaml-csharp-textblock-break-word-wrap

    Private Sub DrawTextblock()
        Dim tb As New TextBlock
        Dim nl As String = Environment.NewLine
        tb.Text = "初期状態グループの構造" & nl & "G5" & nl & "┣G3" & nl & "┃┣G1" & nl & "┃┃┣t0" & nl &
            "┃┃┗t1" & nl & "┃┣G2" & nl & "┃┃┣t2" & nl & "┃┃┗t3" & nl &
            "┃┣t4" & nl & "┃┗t5" & nl & "┗G4" & nl & " ┣t6" & nl &
            " ┣t7" & nl & " ┣t8" & nl & " ┗t9"
        canvas1.Children.Add(tb)
        Panel.SetZIndex(tb, -1)
    End Sub

    Private Sub ReSet()
        Call AddGroup(tList2.GetRange(0, 2), 1) 'g1(0,1)、        t0,t1をグループ化、名前はg1
        Call AddGroup(tList2.GetRange(2, 2), 2) 'g2(2,3)
        Call AddGroup(tList2.GetRange(0, 4), 3) 'g3(g1,g2)、      g1とg2をグループ化、名前はg3
        Call AddGroup(tList2.GetRange(0, 6), 4) 'g3(g1,g2,4,5)、  g3にt4とt5を追加
        Call AddGroup(tList2.GetRange(6, 2), 4) 'g4(6,7)
        Call AddGroup(tList2.GetRange(6, 4), 4) 'g4(8,9)
        Call AddGroup(tList2, 5)                'g5(g3,g4)、      g3とg4をグループ化、名前をg5
    End Sub


    'E:\オレ\エクセル\WPFでPixtack紫陽花.xlsm_配置_$Q$437
    Private Sub MainWindow_ContentRendered(sender As Object, e As EventArgs) Handles Me.ContentRendered
        'Call DrawGridLine()
        'Panel.SetZIndex(canvas1, -1)
        Call AddExThumb2(9) 't0からt9までの10個のExThumb2を作成してリストに入れる
        Call ReSet()
        Call DrawTextblock()

    End Sub
    'リセット
    Private Sub bt1_Click(sender As Object, e As RoutedEventArgs) Handles bt1.Click
        Call ReSet()
    End Sub


    '選択中のtを含むグループを解除
    Private Sub bt2_Click(sender As Object, e As RoutedEventArgs) Handles bt2.Click
        Call ungroup2_1(FocusThumb.GroupTop)
    End Sub

End Class



Public Class ExThumb2
    Inherits Thumb
    Public Property GroupTop As Group2 '全体
    'グループ化するときに元のグループをスタックしていって
    'グループ化解除するときに取り出してGroupTopに据えるGroupStack
    Public Property GroupStack As New Stack(Of Group2)

    Public Sub New(i As Integer, s As Size)
        Me.Name = "t_" & i
        Me.Template = GetTemplate() 'Template指定
        Me.ApplyTemplate() 'Template再構築実行

        Dim b As New Border With {
            .Background = Brushes.Cyan,
            .BorderBrush = Brushes.Black,
            .BorderThickness = New Thickness(1),
            .Width = s.Width, .Height = s.Height}
        Dim tb As New TextBlock With {.Text = "t" & i}
        Canvas.SetLeft(tb, 10)

        Dim c As Canvas = DirectCast(Me.Template.FindName("cc", Me), Canvas)
        c.Children.Add(b)
        c.Children.Add(tb)
    End Sub


    'Template作成
    'WPFとVB.NET、ControlTemplateをコードで作成 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
    'http://blogs.yahoo.co.jp/gogowaten/14156250.html
    Private Function GetTemplate() As ControlTemplate
        Dim ct As New ControlTemplate
        ct.VisualTree = New FrameworkElementFactory(GetType(Canvas), "cc")
        Return ct
    End Function
End Class


Public Class Group2
    Public Property Items As New List(Of ExThumb2)
    Public Property AllItems As New List(Of ExThumb2) 'すべてを取得する時用
    Public Property Groups As List(Of Group2)

    <System.ComponentModel.Category("name")>
    Public Property aName As String
    Public Sub New(i As Integer)
        aName = "G_" & i
    End Sub
End Class



#Region "Group3(未使用)"
Public Class Group3
    Inherits List(Of ExThumb2)
    Public Property Groups As List(Of Group3)
    <System.ComponentModel.Category("name")>
    Public Property aName As String
    Public Sub New(i As Integer)
        aName = "G_" & i
    End Sub
End Class
#End Region
 
 
前回までの方法だとうまく解決できない問題が出た
イメージ 16
イメージ 3
1→2→5の後にグループ化解除なら問題なけど、1から5までの順番だと6で縦横の拡大率がおかしくなる
これは各画像の表示を 
Thumb.Template
	┗Canvas
		┗Image
こうしていて、グループ化した時は
Thumb.Template(c)
	┗Canvas
		┣Thumb.Template(a)
		┃	┗Canvas
		┃		┗Image
		┗Thumb.Template(b)
			┗Canvas
				┗Image
 
 
こうなっていて
グループ化解除時に回転や拡大率の変形情報の引き継ぎがうまくできていないのがおかしくなる原因みたい
解決するのは難しそうだったので別の方法が今回のもので
グループ化は擬似的なものに変更したのが大きな違い
前回はグループ化するごとにThumbが増えていったけど、今回のは増やさないでどれとどれが同じグループですっていうグループ情報を各Thumbに持たせることにした
あるThumbを回転させるときグループに属していたら同じグループ内のThumb全部も同じように回転させる
 
そのグループ情報はこの部分、Group2って名前をつけたClassを作成
イメージ 4
Itemsにはグループ直下のThumbすべてを入れる、グループ化解除の時使う
AllItemsはグループ全体に入っているThumbすべてを入れる
これを使えば移動や変形の指定をグループ全体のThumbにできる
Groupsはこのクラス自体のリスト、グループ同士をグループ化した時に使う
aNameはただの識別用の名前を入れるだけのもの
 
このGroup2を持たせるのがThumbを継承させたExThumb2って名前をつけたこれ
イメージ 5
201行目にあるGroupTopにGroup2を入れる
これでGroupTop.AllItemsってすれば自分が属しているグループ全体のThumbすべてを取得できる
204行目はグループ化解除の時に活躍、CollectionのStackっていうもので初めて使うもの、これが面白い動きをしてアイテムを追加するのは他のリストとあんまり変わらないけど、取り出すときは最後に入れたものから取り出して、取り出したアイテムをリストに残すか削除するかを選べる
205行目以降は装飾的なものなのでグループ化とは関係ないところ
 
 
グループ化
※tはExThumb2を表す、Gはグループを表す
どのtをグループ化するのかって指定するからグループ化メソッドの引数は複数のtになる
 
グループ化の種類
  1. t同士をグループ化
  2. 複数Gのグループ化
  3. 単体Gとtをグループ化
1と2の時は新しいグループを作成
3の時は単体Gにtを統合(入れる)、つまり新しくグループを作成しない
これを判定するには引数のtにグループがいくつあるのか取得する必要がある
それがGetAllGroup2って名前をつけたこれ
イメージ 7
すべてのt.GroupTopをリストに追加して
85行目でDistinctメソッドで重複のないのが得られる
これで渡されたtに含まれる重複のないすべてのG取得
 
グループ直下に加えるtを取得する
 
イメージ 8
上のふたつを使って実際のグループ化の処理
 
 
 
イメージ 11
作成、または統合したGroup2(グループ情報)を渡されたすべてのThumbのGroupTopに設定する
131行目が重要、新しいGを作ったので今までのGは1階層下になるのでGroupStackプロパティにPushメソッドで今までのGを追加、これによって解除するときは1階層上げることになるのでその時取り出しやすくなる
132行目、新しいGをGroupTopプロパティに指定する
 
 
グループ化したいtを渡す
 
イメージ 6
tList2にはすべてのExThumb2が入っているリストになる
この中からグループ化したいtを指定して渡す
ここでは最終的にt0からt9までの10個全てをグループ化している
 

f:id:gogowaten:20191030121313p:plain

左が初期状態から右端は174行目の処理が終わった時の状態
 
GetRangeメソッドはリストから指定範囲のアイテムを取り出してくれる
(0,2)なら0番から2個分の0と1
(2,2)なら2番から2個分の2と3
(6,4)なら6番から4個分の6,7,8,9
 
 
グループ化解除
イメージ 9
解除するときは解除したいGを渡して処理
 
G3を解除する場合
 
イメージ 10
左から右状態になればいい
 
t4とt5はG3直下で下の階層GはないのでGroupStackプロパティには何も入っていない=0、なのでGroupTopを削除するだけでいい、これが145行目
t0,t1はG1、t2,t3はG2がそれぞれGroupStackに入っているので
147行目でGroupStackのPopメソッドで下階層だったGを取り出して
148行目でGroupTopプロパティに指定
Popメソッドは最後に入れたものから取り出して、取り出したアイテムはStackから削除してくれる
 
 
 
イメージ 13
初期状態
すべてのtはG5に入っているので
どのtを選択しても選択GはG5になるし
どれをマウスドラッグ移動しても全部まとまって動く
ここで選択グループを解除すると
イメージ 14
G5が解除されてG3,G4に別れる
t6からt9のどれかをクリックしてから解除すると
 
イメージ 15
G4が解除されてt6からt9は別々にドラッグ移動できるようになる
どのグループにも属していないのでNothingになる
 
今回はここまでで肝心な回転とかの変形はまだなんだよねえ
前回の記事から10日くらい経っているんだなあ
Public Class Group2
    Public Property Items As New List(Of ExThumb2)
    Public Property AllItems As New List(Of ExThumb2) 'すべてを取得する時用
    Public Property Groups As List(Of Group2)
End Class
これにたどり着くまで時間がかかった、とくに
    Public Property Groups As List(Of Group2)
これ、自分と同じClassをプロパティに持つってのがね、なんかこれでいいのかなってムズムズする、無限ループみたい
 
 
今回参照したところ
WPF: XAML, C# で TextBlock などの要素内の文字列を改行させる « をぶろぐ
http://tetsuwo.tumblr.com/post/59191241888/wpf-xaml-csharp-textblock-break-word-wrap
感謝!
 
 
 
今回のコード一式
 
前回までの方法(なんか違う)のコード一式
 
 
関連記事、古い順
2週間前
WPFVB.NET、エクセルのグループ化を真似したいからまずはグループ化のRectを取得 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14151447.html

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

前回の記事
WPFVB.NET、エクセルのグループ化とグループ化解除を真似したい2 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14161262.html
 
翌日
WPFVB.NET、マウスドラッグ移動で範囲選択、枠表示して枠内のものを取得 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14190065.html
↑の記事と今回の記事を合わせてできたのが↓↓
WPFVB.NET、エクセルのグループ化とグループ化解除を真似したい4 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14203583.html
 
2016年6月3日追記
130行目をこっそり修正
誤            For Each t As ExThumb2 In tl
正            For Each t As ExThumb2 In allt