Quantcast
Channel: 午後わてんのブログ
Viewing all 420 articles
Browse latest View live

WPFはスゴイ級、次のPixtack紫陽花はWPFで作成

$
0
0
今まで作ったアプリは「Windowsフォームアプリ」って言うものだったらしい
らしいってのは、あんまり気にしてなかったからなんだけど
最近知っておっこれいいじゃん!ってなったのが「WPFアプリ」
言語はVisual Basicも使えるので
Windowsフォーム+Visual Basic(今まで)
WPF+Visual Basic(最近知った)
ってことなのかな、同じWindows用アプリの作り方にも色いろあるんだなあと

WPFがスゴイのは
半透明の処理を自動で行ってくれる!

半透明の色で塗ったRectangleを重ねて表示したところ
イメージ 1
WPFだとこんなふうに自動で半透明処理して表示してくれる!

そんなWPFアプリ作成はWindowsフォームアプリを作るのに使っていた
Visual Studio Community 2015
これでできるしVisual Basicも使える

Visual Studioのアプリの新規作成の画面で
イメージ 2
これが今までのWindowsフォームアプリの新規作成
その下にあるのが
イメージ 3
WPFアプリ
すっごい近くにあったw
WPFアプリって言われてもWindows用のアプリが作れるなんて気づかないよw
Windows Presentation Foundationの頭文字でWPFで
登場は約10年前と結構昔からあったんだなあ

デザイン画面とXAML
イメージ 4
Windowsフォームアプリのデザイン画面に似ている
XAML(ザムル)って言う言語?で記述していく
StackPanelにボタンを4つ並べたところ

ツールボックス
イメージ 5
ツールボックスもあってここからコントロールを追加できるのも
Windowsフォームアプリにそっくり

プロパティ
イメージ 8
プロパティの画面もあってここからコントロールの
背景色の指定やイベントの指定もできる、これもそっくり

プログラム書くところ
イメージ 6
さっきのXAMLの画面でF7キー押すと表示される画面
ここにプログラムコードを書いていく
Visual Basicで書けるけど
Windowsフォームアプリとはやっぱり違う
Visual BasicでWPFのコントロールを動かすってことなのかなあ

半透明■Rectangleを表示してみる
イメージ 7
デザイン画面とXAML
半透明の赤の■Rectangleの記述は
<Rectangle Fill="Red" Opacity="0.5" Width="100" Height="100" Canvas.Top="10" Canvas.Left="20"/>
Fillが塗る色、Opacityが透明度、あとは大きさと位置の指定だけ
これだけで半透明の処理をしてくれる
Windowsフォームアプリのコントロールのプロパティにも透明度を指定する
OpacityやTransparentがあったけど自分自身に対しての透明度だったので
他のコントロールと重なっても半透明にはならなかった

デザイン画面
イメージ 9
半透明の紫のRectangleを15度回転させる記述
<Rectangle Fill="Purple" Opacity="0.5" Width="100" Height="100" Canvas.Top="50" Canvas.Left="50" RenderTransformOrigin="0.5,0.5">
                <Rectangle.RenderTransform>
                    <RotateTransform Angle="15"/>
                </Rectangle.RenderTransform>
            </Rectangle>
こういう回転して表示するのもたったこれだけでできる

プロパティ画面を使って回転
イメージ 10
赤の■Rectangleを選択してプロパティ画面の変換のAngleを10にしたところ
XAMLには自動で記述されてデザイン画面にも反映される
これならよくわからんRenderTransformとかの英語を覚えていなくても楽にできる


Pixtack紫陽花を作り直したくていろいろ調べていたらWPFってのがいいらしい
ってことで試しに半透明のコントロールを重ねてみたら…
自動で半透明処理してくれた、スゴイ!ってなって
この1ヶ月くらいずーっとWPFだった
次のPixtack紫陽花はWPFで作るって決めた



WPF、アプリのウィンドウに画像ファイルドロップで画像表示テスト

$
0
0
WPFとVBで
エクスプローラなどから画像ファイルドロップしてウィンドウに画像を表示

参照したところは
方法: RichTextBox コントロール上にドロップしたファイルを開く
    https://msdn.microsoft.com/ja-jp/library/hh144798(v=vs.110).aspx
よしいずの雑記帳WPF のサンプルコード 画像ファイルの読込と保存
http://yoshiiz.blog129.fc2.com/blog-entry-818.html
片鱗懐古のブログ:   wpf : 画像ファイルをロックせずにBitmapSourceを読み込む
http://pieceofnostalgy.blogspot.jp/2012/02/wpf-bitmapsource.html
この辺り
WPF関連でググッて見つかるところは圧倒的にC#が多くてVBはかなり少ない
上記の参照したところも下2つはC#
もっとVB増えてほしいなあ

今回テストで作ってみたのがこれ
イメージ 2
アプリのウィンドウに画像ファイルをドロップすると

イメージ 3
その画像を表示

デザイン画面
イメージ 4
WindowのAllowDropにチェックを入れてファイルのドロップを受付可能にする
Gridに名前をつける、grid1って名前にした

VBのコード
イメージ 1
Imports System.IO

Class MainWindow
    Private Sub MainWindow_Drop(sender As Object, e As DragEventArgs) Handles Me.Drop
        'ドロップされたファイルのパスを取得
        Dim filesPath() As String = e.Data.GetData(DataFormats.FileDrop)
        'ファイルパスの画像を割り当てたImageコントロールをGridに表示
        Call AddImage(filesPath(0))
    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()
            End With
        End Using
        Return bmp
    End Function

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

エラーの処理していないからウィンドウにドロップされたものが
画像ファイル以外だとエラーになるし
読み込めない種類の画像だったり
解像度がフルHDの100倍くらいの画像でもエラーになる

ドロップされた画像ファイルのパスを取得
画像ファイルからBitmapImage画像を作成
Imageコントロールを作成してSourceにBitmapImage画像を指定する
Imageコントロールをgrid1に追加して表示する

イメージ 5
4から9行目がWindowに何かがドロップされた時に動くDropイベント
6行目でドロップされたすべてのファイルパスを取得していて
ここはWindowsフォームアプリの時と同じだなあ
イメージ 6
複数のファイルをドロップした時に取得したファイルパス
複数ファイルの時でも今回は一つの画像だけ表示するので0番めのだけ表示する

ファイルパスからBitmapImage作成する
GetBitmapImage
イメージ 7
FileStreamに画像ファイルを読み込んで
今回の中で一番わからないこのBitmapImageに読み込んでいる
FileStreamを使うのは画像ファイルをロックさせないため
ロックされると画像を開いている間は元の画像ファイルの削除や
名前変更、移動などができなくなるから

BeginInitとEndInitは最初と最後に必要な物らしい、そういうものなんだなあって納得できる
StreamSourceにFileStreamを指定するのはわかる
わからないのがCacheOption、指定できるのはOnLoadのほかに
Default、None、OnDemandの4つあるけどOnLoad以外だと画像が表示されなかった

ファイルパスから作成した画像を表示するImageControlを作成して
grid1に追加して表示する
AddImage
イメージ 8
さっきのGetBitmapImageにファイルパスを渡してBitmapImageを取得
Imageコントロールを作成してSourceにBitmapImageを指定
Imageをgrid1に追加

33,34行目はImageコントロールの幅と高さを指定
この時はBitmapImage(画像)の幅と高さをもとにするんだけど
幅ならWidthじゃなくてPixelWidthの方がいいみたいで
イメージ 9
イメージ 10
一時停止してBitmapImageの中を見てみる
PixelWidthは560だけど
Widthは560.07816133906931とか変な数値になっている

そんなこんなでできあがったImageコントロールをgridに追加するには
grid1.Children.Add(img)
ってChildrenってのを使うみたい
Windowsフォームアプリのときは
Form1.Controls.Add(img)
だったかな、微妙に違うw

ここまでで画像を表示する最低限のものはできた
画像ファイルから画像を取得する…と言うか画像データを作成するには
他にも方法があるみたいで
BitmapSourceを使う方法、さっきのはBitmapImageで名前似ている
イメージ 11

    'ファイルパスから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.Default)
            bmp = New WriteableBitmap(bd.Frames(0))
            bmp.Freeze()
        End Using
        Return bmp
    End Function

これを付け足して

イメージ 12
28行目をコメントアウトして
29行目を付け足し
これでも画像が表示される

46行目にあるBitmapCacheOption.DefaultはBitmapCacheOption.OnLoadでも
どちらでも画像が表示される
BitmapImageの時はOnLoad以外は表示されなかったんだけど
BitmapSourceだとDefaultでも問題ない、やっぱよくわからん


それぞれのプロパティ
BitmapImage
イメージ 13

BitmapSource(WriteableBitmap)
イメージ 14
青色の1行目のところを見ると
BitmapSourceだと思っていたけどWriteableBitmapっていうのが正しいのかな
2つを比べてわかったのが2つは微妙に違うってことだけで
どっちを使ったらいいのかはさっぱりわからん


今回の全文
イメージ 15


BitmapSource クラス (System.Windows.Media.Imaging)
https://msdn.microsoft.com/ja-jp/library/system.windows.media.imaging.bitmapsource(v=vs.110).aspx
ここみたらBitmapSourceを継承したものがいろいろあって
その中にBitmapImageやWriteableBitmapってことみたい?


この記事の続きは
WPFとVBでアプリ作る準備その1、マウスドラッグでコントロールの移動 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13900213.html


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

$
0
0


イメージ 1

WPFではアプリのウィンドウにボタンとかのコントロールを複数表示させるには
ウィンドウの中にパネル系のコントロールを置いて
その中に色々ボタンとかのコントロールを置いていくみたい
<Window>
<パネル系のコントロール>
<いろいろ>
</いろいろ>
<いろいろ>
</いろいろ>
</パネル系のコントロール>
</Window>

パネル系のコントロールもいろいろある
アプリを新規作成した時に最初から設置されているのがGridなので
Gridが代表格なのかも、その他でよく見かけるのがStackPanel
ツールボックスのコモンコントロールにもこのふたつが入っている
イメージ 7
Canvas、TabControl、WrapPanelとか他にもあるみたい
癖がなくて使いやすそうな感じなのが
GridとCanvasのふたつ
「マウスドラッグでコントロールを移動」とかで
ググってみるとCanvasを使っているケースが多いけど
両方試してみた

このふたつは特に指定しないと置いた場所全体に広がる
Windowに置けばWindow全体に広がる

コントロールのドラッグ移動で必要なのが位置の取得と指定なんだけど
GridとCanvasではこのコントロールの位置の決め方が違う
Gridの場合
イメージ 9
GridにButtonを設置したところ
Marginプロパティで場所指定する

左寄せと上寄せをしないと変な位置になるので
HorizontalAlignment="Left" VerticalAlignment="Top"を指定している
Marginの4つの数字は順番に左、上、右、下の値になる
左寄せと上寄せを指定するとMarginの右と下の値は無視される

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



マウスドラッグでコントロールを移動させる処理の流れ

マウスダウンイベントで
ドラッグ移動中フラグにする
最初のマウスの位置を記録しておく

マウスムーブイベントで
ドラッグ移動中フラグなら
動かした今の位置から最初の位置を引き算して移動距離を測る
移動距離と動かしているコントロールの今の位置を足し算したのが
移動させる位置なのでこれを指定する

マウスアップイベントで
ドラッグ移動中フラグを解除


動かすコントロールはRectangleにしてみた

Grid編
イメージ 2
最初からあるGridにgrid1と言う名前を付けただけ

VBコード
イメージ 3
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とかでそのまま取得できる


Canvas編
イメージ 4
イメージ 5
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コントロールなら
イメージ 10
これだけで動く、実質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コントロールかなあ


ここまでとこの前の
WPF、アプリのウィンドウに画像ファイルドロップで画像表示テスト ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13894909.html
これを組み合わせて作ったのが↓
イメージ 17


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

このテストアプリの中身
デザイン画面
イメージ 18

VBコード
イメージ 19
コピペして作ったからコメントアウトでの説明が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)

    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つは必要な機能




WPFとVBでアプリ作る準備その2、ControlTemplateの中のControlを取得する

$
0
0
動的作成したコントロールにテンプレートを指定する
その後からテンプレートの中のコントロールを取得していろいろ設定を変更

目的は画像ファイルがアプリのウィンドウにドロップされたら表示して
マウスドラッグで移動させること…前回と一緒なんだけど

画像ファイルを表示するのに適したコントロールはImage
マウスドラッグで移動させるコントロールで最適なのはThumb
どちらも目的には一長一短
このふたつを組み合わせて使いたい
ググったらControlTemplate(テンプレート)ってのを使えばできそうで
なんとかできた感じ

画像ファイルドロップで表示しするのは後回しにして
ThumbとImageを組み合わせたものに画像を表示してみることから
イメージ 1
Thumbコントロールの中にImageコントロールを使ったTemplateを入れる
これで画像の表示はImageコントロールに任せたThumbコントロールができる
Thumbコントロールの名前はthumb1
Imageコントロールの名前はimage1にした
image1に表示する画像ファイルの指定はボタンコントロールのクリックイベントにした
ボタンクリックイベント中身は
イメージ 2
    Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
        Dim bmp As New BitmapImage(New Uri("D:\ブログ用\テスト用画像\hueRectT210.png"))
        Dim img As Image = thumb1.Template.FindName("image1", thumb1)
        img.Source = bmp
        img.Width = bmp.PixelWidth
        img.Height = bmp.PixelHeight
    End Sub


画像を表示するにはControlTemplateの中にあるimage1のSourceプロパティにBitmapImage(画像)を指定すればいいだけ
image1を取得するには
Template.FindNameメソッドを使う
thumb1の中のControlTemplateの中のImage1を取得するので
Dim img As Image = thumb1.Template.FindName("image1", thumb1)
これでOK

画像ファイルのパスからBitmapImage(画像)の作成
        Dim bmp As New BitmapImage(New Uri("D:\ブログ用\テスト用画像\hueRectT210.png"))

あとはimage1のSourceにBitmapImageを指定すればthumb1に画像が表示される
        img.Source = bmp


実行したところ
イメージ 3
ボタンクリックすると

イメージ 4
画像が表示される
OK



後回しにしていた画像ファイルドロップで表示するには
動的に作成したThumbコントロールにTemplateを指定する
って方法でできた
でもVBのコードでTemplateを作成する方法がわからなかったので
デザイン画面のXAMLの方でTemplateを用意しておいて
それをコードのほうで取得して指定することにした

デザイン画面でTemplateの用意したところ
イメージ 5
Window.Resourcesの中にControlTemplateを入れてその中にImageを入れる
ControlTemplateのKeyをctにして
ImageのNameをimage1にしてみた
KeyやNameはVBのコードからこのControlTemplateやImageを取得するときに必要になる

動的作成したThumbにこのControlTemplateを指定して
その中のimage1に画像を指定すればThumbに画像が表示されることになる

VBコード
イメージ 6
Imports System.Windows.Controls.Primitives 'Thumb用
Imports System.IO

Class MainWindow

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

    Private Sub thumb_Loaded(sender As Object, e As RoutedEventArgs)
        Dim t As Thumb = sender
        Dim bmp As BitmapImage = GetBitmapImage(t.Tag) 'Tagプロパティから取得した画像ファイルのパスから画像作成
        '取得したいコントロールの名前を指定してテンプレートの中から取得
        'テンプレートの中のコントロールを取得するTemplate.FindName
        Dim img As Image = t.Template.FindName("image1", t) 'テンプレートの中のコントロール取得
        img.Source = bmp
    End Sub


    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) 'Thumbを表示する位置
        For i As Integer = 0 To filesPath.Length - 1
            Call AddThumb(filesPath(i), locate) 'Thumb作成表示
            locate.Offset(30, 30) '位置の変更
        Next
    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

    'ThumbControlを作成して追加
    Private Sub AddThumb(filesPath As String, locate As Point)
        Dim t As New Thumb
        canvas1.Children.Add(t)
        'Resourcesの中にある取得したいTemplateのKeyを指定してThumbのTemplateに指定する
        t.Template = Me.Resources.Item("ct")

        t.Tag = filesPath 'Tagプロパティに画像ファイルのパスを入れておく
        Canvas.SetLeft(t, locate.X) '表示する位置は必須、指定しないとDragdeltaイベントで移動量が取得できない
        Canvas.SetTop(t, locate.Y)  '必須

        '作成した直後の今のタイミングでテンプレートの中のコントロールを取得しようとするとうまくいかないので
        'Loaded(作成完了直後)の時に取得するようにLoadedイベント時のメソッドを指定する
        AddHandler t.Loaded, AddressOf thumb_Loaded
        AddHandler t.DragDelta, AddressOf Thumb_Dragdelta 'これはマウスドラッグ用

    End Sub
End Class
例外処理してないけど
画像ファイルドロップで表示した画像をマウスドラッグで移動できるようにした


    'ファイルパスからBitmapImage(画像)を作成して返す
    Private Function GetBitmapImage(filePath As String) As BitmapImage
イメージ 10
これは前回の
WPFとVBでアプリ作る準備その1、マウスドラッグでコントロールの移動 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13900213.html
この記事の時と同じかな
ファイルをロックしないようにfilestreamを使ってBitmapImageを作成

   
 'ドラッグ移動
    Private Sub Thumb_Dragdelta(sender As Object, e As DragDeltaEventArgs)
イメージ 11
Thumbコントロールをマウスドラッグで移動する時用
他のコントロールよりラクに動かせるのがThumbコントロール


ドロップされたパスをすべて取得する
    Private Sub MainWindow_Drop(sender As Object, e As DragEventArgs) Handles Me.Drop
イメージ 7
これも前回の記事と同じかな違うのはImageを作るのがThumbに変わったところ
28行目の
Call AddThumb(filesPath(i), locate) 'Thumb作成表示



Thumbコントロールを作成してCanvasに追加する
Private Sub AddThumb(filesPath As String, locate As Point)
イメージ 8
53行目、Resources.Item("ct")
ctというKeyを持ったControlTemplateを取得すると同時に
これをt(Thumb)のTemplateに指定している

あとはTemplateの中のImageを取得して画像を指定したいけど
作成完了してからじゃないとできないみたいなので
Loadedイベントでするようにしているのが61行目の
        AddHandler t.Loaded, AddressOf thumb_Loaded
ここで困ったのがLoadedイベントの時にどうやって画像ファイルのパス取得するか
いろいろ方法はあるけど、今回は作成したThumbのTagプロパティに持たせることにしたのが55行目
        t.Tag = filesPath 'Tagプロパティに画像ファイルのパスを入れておく

ここが終わるとLoadedイベントが発生する


    Private Sub thumb_Loaded(sender As Object, e As RoutedEventArgs)
イメージ 9
ThumbができあがったらLoadedイベントが発生するのでここで画像を読み込んで指定する
画像取得はGetBitmapImageメソッドに投げる、この時必要なファイルパスはTagプロパティに入れておいたものを使っているのが14行目
        Dim bmp As BitmapImage = GetBitmapImage(t.Tag) 

(Thumb(t)のTemplateの中のControlを取得するには
Template.FindNameに取得したいControlの名前を指定する
今回取得したいImageにはimage1という名前を付けておいたので
17行目の
t.Template.FindName("image1", t)
これで取得できている
あとは取得したImage(img)のSourceプロパティに画像をしてすれば全て完了


今回もいろいろ新しいのを使った
デザイン画面のXAMLでは
Window.Resources
ControlTemplate
VBのコードでは
Resources.Item

一番難しかったのがControlTemplateの中のControlを取得する
Template.FindName
動的に作成しているControlが作成完了した後なら使えるけど
作成している途中では使えないってのがわからなくて諦めかけてたところに
片鱗懐古のブログ: wpf : TemplateからNameを元にコントロールを探す
http://pieceofnostalgy.blogspot.jp/2012/05/wpf-templatename.html
どうやらインスタンス化してからじゃないとできないみたい
よくわかんないけど見よう見まねでなんとかなった次第


イメージ 12
画像ファイルドロップして
表示された画像をマウスドラッグしているところ

イメージ 13
こういう普通っぽい画像でgif動画を作ると簡単に1MBを超えちゃうんだよねえ
上のgif動画は0.2MB


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

完了したテスト
  • 画像ファイルドロップで画像表示
  • 画像をマウスドラッグで移動
全然進んでいないように見えるw



参照したところ
[C#] WPFのThumbコントロールを使ってドラッグを実装する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3701
M'sLabo report: コードで生成したコントロールに静的リソースを適用する方法
http://mslaboratory.blog.eonet.jp/default/2012/11/post-4482.html
片鱗懐古のブログ: wpf : TemplateからNameを元にコントロールを探す
http://pieceofnostalgy.blogspot.jp/2012/05/wpf-templatename.html


WPF_test3 (master) - Visual Studio Team Services
https://gogowaten.visualstudio.com/DefaultCollection/WPF/_git/WPF_test3?path=%2F&version=GBmaster&_a=contents
コードはここにおいてみた、自分以外でも見えるかな
Wpf_test53_コントロールテンプレートの中のコントロール取得
ってのが今回のもの
Gitってのは便利らしいけど使い方がわからなくてただのファイル置き場みたいになっているw







WPFとVBでアプリ作る準備その3、ImageコントロールにDragDeltaイベントがほしくて擬似DragDelta

$
0
0
3回めになるコントロールのドラッグ移動
また少し思い描いていたものに近づいた
画像ファイルドロップで表示した画像をマウスドラッグできるようにするのに
ImageコントロールにDragDeltaイベントがあればいいなあ
でも方法がわからないなあってのが少しなんとかなったかも

イメージ 19
動きは前回、前々回と全く同じはず


Imageを継承したクラスを作成して、OnMouseMoveメソッドを
Overridesして、その中でDragDeltaイベントを発生させて
DragDeltaイベントがあるように見せかけるっていうの

Public Class ExImage
    Inherits Image
Public Event ExDragDelta(sender As Object, e As DragDeltaEventArgs)
Protected Overrides Sub OnMouseMove(e As MouseEventArgs)
RaiseEvent ExDragDelta(Me, New DragDeltaEventArgs(p.X, p.Y))
End Sub
End Class

こんなかんじ
もっとまともな方法があると思うんだけどわからないし
OverridesとかRaiseEventとか理解できていないんだけどね
今のところ期待通りに動いている
全体のコードの様子は
Imageクラスを継承して作ったExImageクラス
イメージ 1
名前はExImageにした

Imports System.Windows.Controls.Primitives

Public Class ExImage
    Inherits Image
    Private _p As Point
    Public Event ExDragDelta(sender As Object, e As DragDeltaEventArgs)
    Protected Overrides Sub OnMouseLeftButtonDown(e As MouseButtonEventArgs)
        MyBase.OnMouseLeftButtonDown(e)
        _p = 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), _p)
            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
DragDeltaEventArgsを引数に持たせたExDragDeltaと言う
名前を付けたイベントを宣言?しておく

OnMouseLeftButtonDownで
左クリックした時にクリックした位置を記録して

OnMouseMoveで
マウスが動いた時に今の位置と記録した位置から移動距離を出して
RaiseEventでExDragDeltaを起動する、この時引数に移動距離を持たせる


ドラッグ事にマウスを早く動かし過ぎると動かしているコントロールから
マウスが離れないようにするために
左クリック時にMe.CaptureMouseでくっつけて
クリックを離した時にMe.ReleaseMouseCaptureで放している


デザイン画面
イメージ 15
前回と一緒でWindowにAllowDropをTrue指定して
canvas1って言う名前をつけたCanvasを設置しただけ

VBコード
イメージ 16

Imports System.IO
Imports System.Windows.Controls.Primitives

Class MainWindow
    'マウスドラッグ移動
    Private Sub ExImage_DragMove(sender As Object, e As DragDeltaEventArgs)
        Canvas.SetLeft(sender, e.HorizontalChange + Canvas.GetLeft(sender))
        Canvas.SetTop(sender, e.VerticalChange + Canvas.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 AddExImage(filesPath As String, locate As Point)
        Dim t As New ExImage
        canvas1.Children.Add(t)
        t.Source = GetBitmapImage(filesPath)
        Canvas.SetLeft(t, locate.X) '表示する位置は必須、指定しないとDragdeltaイベントで移動量が取得できない
        Canvas.SetTop(t, locate.Y)  '必須
        AddHandler t.ExDragDelta, AddressOf ExImage_DragMove 'これはマウスドラッグ用
    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 AddExImage(filesPath(i), locate) 'ExImage作成表示
            locate.Offset(30, 30) '位置の変更
        Next
    End Sub

End Class


イメージ 17
ドラッグ移動の部分は
まるでDragDeltaイベントが有るかのような振る舞いw
なのでThumbコントロールの時と全く同じで簡潔になっている

今気づいた
イメージ 20
Canvas.SetLeftとかのCanvasって部分は
Imports System.Window.Controls.Canvasって書いとけば
SetLeftだけでも動く


イメージ 18
やっていることはほとんど同じだから、あんまり変わらないかな?

今回の方法はよくわかっていないので
なにか気づいていない良くない副作用があるかもしれないけど
気に入っているからこれで行こうかなあと


参照したところは
VB イベントの作成 - Event, RaiseEvent, WithEvents
http://homepage1.nifty.com/rucio/main/dotnet/shokyu/standard49.htm
アプリが作れるようになったのはこのVB中学校のおかげ
あれから2年経ったけど未だに初級講座のところも理解できていないw
デリゲート…インターフェイス…マルチスレッド(゚д゚)?



WPF_test4 (master) - Visual Studio Team Services
https://gogowaten.visualstudio.com/DefaultCollection/WPF/_git/WPF_test4
Wpf_test55_Imageコントロールを擬似DragDeltaでマウスドラッグ移動






クラスを追加するときは
イメージ 2
メニューのプロジェクト→クラスの追加

イメージ 3
適当な名前(Class1とか)を付けて追加ボタン

イメージ 4
Class1クラスが追加された

作った後からクラスの名前を変える時は
イメージ 5
クラスの名前を右クリック→名前の変更

イメージ 6
クラスの名前が選択された状態になるので書き換える

イメージ 7
エンターキーを押せば完了
このクラスを参照しているところも全て変更される

ファイル名?も変更する
イメージ 8
ソリューションエクスプローラに表示されている名前も変更するには

イメージ 9
ソリューションエクスプローラに表示されているクラスを右クリック
→名前の変更

イメージ 10
編集できるようになる

イメージ 11
変更できた



MouseMoveイベントの時の動作を改変する?には
OnMouseMoveメソッドをOverrides
イメージ 12
Overridesって打ってスペース入れるとOverridesできるもの一覧が表示される
続けてMoveって打つと

イメージ 13
絞込される、OnMouseMoveを選択してTabキーを押すと

イメージ 14
最低限必要なことなんだろうと思われるものが自動で入力される





























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

$
0
0
コントロールを上下に重ねた時の位置関係を指定する
WPFではZIndexプロパティで指定する

テスト結果
イメージ 25
選択画像のZ位置をボタンで変更


ZIndex(上下の位置)を指定しない場合
イメージ 1
ボタンを3つ重なるように配置した状態
ZIndex(上下の位置)を指定しないと最初に配置したものが一番下になって
後に配置したものが上に重なっていく

イメージ 2
指定しない時のZIndexの値は0になっている
ここからbutton1のZIndexを1にすると
イメージ 4
button1が一番上になる

拡大
イメージ 3
ZIndexを文字で指定するときはPanelを付けて
Panel.ZIndex="1"とかになるみたい。


大きい数値が指定されたものほど上に表示される
マイナスの数値も指定できる
同じ数値の者同士なら後に設置されたものが上になる

button1をクリックしたらZIndexを1に変更するをVBのコードで書く場合
デザイン画面
イメージ 5
button3の位置を少し変えて、button1のZIndexを指定なしにした状態。
button1をダブルクリックすると

イメージ 6
この画面になるので

イメージ 7
        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
などを用意して

デザイン画面はこうしてみた
イメージ 10
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行目から最後までが今回書き加えたところ
イメージ 11
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コード
イメージ 14
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

イメージ 17
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倍以上はあったなあ


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




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

$
0
0
コントロールの大きさや位置の合わせてスクロールバーを表示する
前回の記事
WPFとVBでアプリ作る準備その4、コントロールを重ねた時の上下移動(ZOrder)はPanel.SetZIndex ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13913897.html


画像をウィンドウの外側に移動したとき
イメージ 1
外側に出たままになっているこれを

イメージ 22
スクロールバーを自動で表示する

デザイン画面とXAML
イメージ 23

MainWindow
イメージ 25
ふたつの赤いところが前回から書き加えたところ
灰色のところは必要ないところ(選択画像を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
イメージ 24
ふたつの赤いところが前回から書き加えたところ
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





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

イメージ 3
このCanvas部分を

イメージ 4
こうすると

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

左上に寄せたいから
イメージ 14
CanvasのVerticalAlignmentをTop
HorizontalAlignmentをLeft
イメージ 13


スクロールバーを適切に表示するには
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
これで動いている

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




WPFとVBでアプリ作る準備その6、Canvas内に表示している複数画像を1枚の画像にして保存

$
0
0
Canvas内に表示されている画像を保存

イメージ 1
保存ボタンをクリックで

イメージ 2
保存ダイアログ表示

イメージ 3
ファイル名を付けて画像形式を選択して保存ボタンクリックで保存される

png画像形式
イメージ 5
空白の部分は透明になるけど写真画像だとファイルサイズが大きくなる
画像保存時に画質劣化は基本的にない

jpg、Jpeg画像形式
イメージ 4
半透明や透明ができないので空白部分は黒くなるか指定した色になるけど
写真画像向き、保存時に画質劣化するけど画質(QualityLevel)を指定できる


デザイン画面とXAML
イメージ 6
保存ボタンを付け加えた、名前はsaveにした

ExImageクラス
イメージ 7
前回から変化なし

MainWindow
イメージ 8
赤いところが今回書き加えたところ

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

    '        キャンバスに描いた絵を画像ファイルとして保存する | 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

canvas1に表示されている画像を保存するメソッド
SaveAllimage
イメージ 9
セーブダイアログ作成
New Microsoft.Win32.SaveFileDialog

141:
.Filter = "*.png|*.png|*.jpg|*.jpg;*.jpeg|*.bmp|*.bmp|*.gif|*.gif|*tiff|*.tiff"
これが画像形式(ファイルの種類)を選択するところになる
イメージ 10
142:AddExtension=True
ファイル名に自動で拡張子をつける

描画関係、この辺はよくわかっていない
イメージ 11
ExImage(画像)を表示してあるcanvas1自体を使ってVisualBrushを作成して
そのブラシを使ってDrawingContextの四角形に塗っている?のが154行目
そのあと、158行目でRenderTargetBitmapを作成して
159行目でそこにまた塗っている?
なんか冗長な感じがするw
でもこれでBitmapができたっぽい

エンコーダ選択して作成
イメージ 12
さっきのファイルの種類の設定で指定したFilterは順番に1から始まるインデックスが
付けられているのでそのインデックスからそれぞれの形式のエンコーダを作成
2番のJpegだけは画質設定ができたので97に指定してみた
この辺りもWindowフォームアプリのときに比べるとWPFはかなりラクになっている

エンコーダにBitmapを渡して保存
イメージ 13
159行目でできあがったBitmapを使ってBitmapFrameを作って
エンコーダのFramesに追加
ファイルストリームを使ってエンコーダのSaveメソッドで保存
やっとできた

Dim vb As New VisualBrush(canvas1)
っていうこれだけでcanvas1のブラシが作れるのはスゴイ便利な気がする
試しにこのcanvas1部分をGridのgrid1に変更して
Dim vb As New VisualBrush(grid1)

イメージ 14
この状態で保存した画像が
これ↓
イメージ 15
サイズが違うけどしっかりgrid1にあるものが描画されている

148            Dim canvasRect As Rect = GetUnion(CollectionExImage)
149            canvasRect = New Rect(grid1.RenderSize)
こうして保存したら
イメージ 16
こうなったw
画像自体は簡単に取得できる

半透明の画像
イメージ 18
これをpng形式で保存すると

イメージ 17
こうなる
WPFの自動透過処理は素晴らしい



予定しているテスト
  • ドラッグ移動時に指定ピクセル数ごとに移動
  • 指定した画像の表示を消す(削除)
  • 指定した色を透明にする
  • レイアウト

完了したテスト
  • 画像ファイルドロップで画像表示
  • 画像をマウスドラッグで移動
  • 表示画像のレイヤー間の移動(入れ替え)ZOrder指定
  • 表示画像すべてを画像ファイルとして保存←New

WindowフォームアプリのPixtack紫陽花と比べるとかなりあっさりで
コード量だと5倍くらい違うかも
Pixtack紫陽花は最終的に3万行を超えて手がつけられなくなったw
Pixtack紫陽花は初めて作ったアプリだったせいもあるけど
やっぱりWPFの機能がすごいからラクにできる部分が大きい


参照したところは
キャンバスに描いた絵を画像ファイルとして保存する | 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






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
















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

$
0
0
コントロールをマウスドラッグ移動のとき指定ピクセル数おきに移動させる
見えないマス目(グリッド)に合わせる移動

イメージ 1
グリッドの数値を変化させて
100x100の画像を移動しているところ


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

デザイン画面とXAML
イメージ 2
赤いところが前回から追加したところ
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クラス
イメージ 3
前回から変化なし

MainWindowクラス
イメージ 5

水色の範囲が今回書き加えたところ
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
イメージ 7
マウスドラッグ移動の時にグリッドに合わせる
さっきのAjustGridメソッドで移動前の位置はグリッドに合っているはずなので
マウスの移動距離がグリッドぶん動いた時にその位置に移動させればいいってことで
マウスの移動距離÷マス目間隔の余り = 0になった時に移動
ってのが最初の方法だったけど動きがいまいち
どうやら0になった瞬間ってのがよくなさそうだった

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


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

イメージ 9
イメージ 10

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

イメージ 12
Pixtack紫陽花を作ったんだけどWPFでもやっとそれっぽくなってきた



予定しているテスト
  • 指定した画像の表示を消す(削除)
  • 指定した色を透明にする
  • レイアウト

完了したテスト
  • 画像ファイルドロップで画像表示
  • 画像をマウスドラッグで移動
  • 表示画像のレイヤー間の移動(入れ替え)ZOrder指定
  • 表示画像すべてを画像ファイルとして保存
  • ドラッグ移動時に指定ピクセル数ごとに移動←New



WPFとVBでアプリ作る準備その8、コントロールの削除、変数の中身が変化した時に何かの処理をしたい時はPropertyのSetで

$
0
0
コントロールの削除

イメージ 1
選択画像の削除

前回の記事
WPFとVBでアプリ作る準備その7、指定した間隔ごとにコントロールをマウスドラッグ移動 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13924059.html



デザイン画面とXAML
イメージ 2
削除ボタンを追加、名前はremoveにした

画像を表示するExImageクラス
イメージ 3
前回から変化なし


MainWindow
イメージ 4
水色の範囲が書き換えや書き加えたところ

Imports System.IO
Imports System.Windows.Controls.Primitives
Imports System.Windows.Controls.Panel
Imports System.Windows.Controls.Canvas


Class MainWindow
    'すべてのExImageを入れておくリストコレクション
    Private CollectionExImage As New ObservableCollectionExImage

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

    '    VB プロパティの作成
    'http://homepage1.nifty.com/rucio/main/dotnet/shokyu/standard47.htm


    '選択中の画像(ExImage)を記録しておくプロパティ
    '選択画像が変わった時に見本画像更新したくて作った
    Private _FocusExImage As ExImage
    Private Property FocusExImage As ExImage
        Get
            Return _FocusExImage
        End Get
        Set(value As ExImage)
            'プロパティに値をセット、つまり中身が変化する
            _FocusExImage = value
            ''FocusExImageの中身が入れ替わった時実行
            Call DisplayUpdateLocate(value)
            '見本画像更新
            If value Is Nothing Then
                mihon.Source = Nothing
            Else
                mihon.Source = value.Source
            End If
        End Set
    End Property



    '削除ボタン押した時
    Private Sub remove_Click(sender As Object, e As RoutedEventArgs) Handles remove.Click
        If FocusExImage Is Nothing Then Return
        Call RemoveExImage(FocusExImage)
    End Sub

    '選択画像削除
    Private Sub RemoveExImage(ex As ExImage)
        Dim i As Integer = CollectionExImage.IndexOf(ex)
        CollectionExImage.Remove(ex) 'リストコレクションから削除
        canvas1.Children.Remove(ex) 'canvas1から削除(表示から消す)
        '削除後、選択画像(FocusExImage)の変更
        'ひとつ下の画像を選択、一番下だったらひとつ上の画像、最後の画像だったらNothingにする
        If i > 0 Then
            FocusExImage = CollectionExImage.Item(i - 1)
        ElseIf i = 0 AndAlso CollectionExImage.Count > 0 Then
            FocusExImage = CollectionExImage.Item(i)
        Else
            FocusExImage = Nothing
        End If
    End Sub


End Class

選択画像の削除で必要なのは
画像(ExImage)を配置している親コントロール(canvas1)から削除
すべての画像を入れてあるリストコレクション(CollectionExImage)から削除
別の画像を選択画像にする

必要じゃないけどあったほうがいいのは
選択画像の見本を表示しているImageコントロールの画像の切り替え

選択画像を削除する
RemoveExImage
イメージ 5
WPFでのコントロールの削除はRemoveメソッドだけでいいみたい
WindowフォームアプリのようにDisposeは必要なさそう、ラクチン
なので削除は282,283の2行で終わり
残りの行は選択画像を別の画像にする処理
基本は削除した画像のひとつ下にあった画像を次の選択画像にしている
イメージ 6
黄色の画像を選択した状態で削除ボタン押すと

イメージ 7
ひとつ下の緑の画像が選択画像になる
緑の画像は一番下なので削除してひとつ下の画像を選択しようとしても
できないので、その時だけは逆にひとつ上の画像を選択することにする

イメージ 8
こうしたい

        If i > 0 Then
            FocusExImage = CollectionExImage.Item(i - 1)
        ElseIf i = 0 AndAlso CollectionExImage.Count > 0 Then
            FocusExImage = CollectionExImage.Item(i)
        Else
            FocusExImage = Nothing
        End If
削除した画像が一番下(最背面)じゃなければ削除した画像のひとつ下の画像
削除した画像が一番下でそれ以外に画像がまだあればひとつ上の画像
画像が1つもなければ空にする
って書いたけど、いま見てたらなんか冗長な感じがする
        If CollectionExImage.Count = 0 Then
            FocusExImage = Nothing
        ElseIf i = 0 Then
            FocusExImage = CollectionExImage(i)
        Else
            FocusExImage = CollectionExImage(i - 1)
        End If
画像が1つもなければ空にする
削除画像が一番下ならひとつ上の画像を選択
それ以外ならひとつ下の画像を選択
あんまり変わらないけどこっちのがいい

これで削除とそれに伴う選択画像(FocusExImage)の入れ替えができた
あとはアプリのウィンドウの左上にある見本画像の変更
これをしないと
イメージ 9
こうなる
この見本画像の変更したい時は削除した時以外にも
クリックで選択画像が切り替わった時がある
つまり二箇所ある、というか今回ので増えて二箇所になったから
それぞれに見本画像の変更する処理を書けばいいんだけど
選択画像が切り替わった時に動くイベントがあれば
その一箇所に書けばラクチンだなあって思って
変数 変更時イベントとかでググったら

VB.NET2008で、変数の変化によるイベント - VB.NETで、Boolea... - Yahoo!知恵袋
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1073962530
プロパティのセットのところで実行すればいい!
これはかなり基本的なことな気がするwでも全然思いつかなかったし
プロパティって自分で書いて使ったことほとんどないから
VB中学校の
VB プロパティの作成
http://homepage1.nifty.com/rucio/main/dotnet/shokyu/standard47.htm
ここを見て書き直した

イメージ 11
前回まではこうだった

イメージ 10
今回、ずいぶん増えたけどこの方がいろいろできるし
ここ一箇所に書けばいいので他の部分が増えなくて済む
これで選択画像(FocusExImage)が切り替わった時っていうのは
この31行目からのSetのことだからこの後に処理を書けばいい
実際に切り替わるのは33行目なのかな、なのでそれ以降に書いた
37行目から41行目が見本画像を切り替える処理

これで見本画像切り替えは選択画像切替時に自動でできるようになり
イメージ 12
前回の152行めが不要になったので削除(コメントアウト)


予定しているテスト
  • 指定した色を透明にする
  • レイアウト

完了したテスト
  • 画像ファイルドロップで画像表示
  • 画像をマウスドラッグで移動
  • 表示画像のレイヤー間の移動(入れ替え)ZOrder指定
  • 表示画像すべてを画像ファイルとして保存
  • ドラッグ移動時に指定ピクセル数ごとに移動
  • 指定した画像の表示を消す(削除)←New

テストと言いつつ継ぎ足しで書いていったから
作っているのとあんまり変わらなくなったなあ


ヤフーブログの文字数制限でコードが全部載せられなかったので
別記事
WPFとVBでアプリ作る準備その8の記事のコード全文 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13926875.html




WPFとVBでアプリ作る準備その8の記事のコード全文

$
0
0
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)

    'Item移動の時
    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



Imports System.IO
Imports System.Windows.Controls.Primitives
Imports System.Windows.Controls.Panel
Imports System.Windows.Controls.Canvas


Class MainWindow
    'すべてのExImageを入れておくリストコレクション
    Private CollectionExImage As New ObservableCollectionExImage

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

    '    VB プロパティの作成
    'http://homepage1.nifty.com/rucio/main/dotnet/shokyu/standard47.htm


    '選択中の画像(ExImage)を記録しておくプロパティ
    '選択画像が変わった時に見本画像更新したくて作った
    Private _FocusExImage As ExImage
    Private Property FocusExImage As ExImage
        Get
            Return _FocusExImage
        End Get
        Set(value As ExImage)
            'プロパティに値をセット、つまり中身が変化する
            _FocusExImage = value
            ''FocusExImageの中身が入れ替わった時実行
            Call DisplayUpdateLocate(value)
            '見本画像更新
            If value Is Nothing Then
                mihon.Source = Nothing
            Else
                mihon.Source = value.Source
            End If
        End Set
    End Property

    '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, p.X - xm) '最寄りの左のグリッドに移動
        End If
        If ym <> 0 Then
            SetTop(ex, p.Y - 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)

        'Pixtack紫陽花と同じ方式
        Dim xIma As Integer = GetLeft(ex) + e.HorizontalChange 'ExImageの横位置 + マウスの横移動距離
        Dim yIma As Integer = GetTop(ex) + e.VerticalChange '縦位置
        '移動先指定
        SetLeft(ex, xIma - (xIma Mod g)) '横位置 - (横位置をグリッド数値で割った余り)
        SetTop(ex, yIma - (yIma Mod g)) '縦位置

        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 = exex.Source '見本を表示
        Call kousin() 'textBlockのZIndex表示更新
    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



    '削除ボタン押した時
    Private Sub remove_Click(sender As Object, e As RoutedEventArgs) Handles remove.Click
        If FocusExImage Is Nothing Then Return
        Call RemoveExImage(FocusExImage)
    End Sub

    '選択画像削除
    Private Sub RemoveExImage(ex As ExImage)
        Dim i As Integer = CollectionExImage.IndexOf(ex) '削除対象のIndex取得
        CollectionExImage.Remove(ex) 'リストコレクションから削除
        canvas1.Children.Remove(ex) 'canvas1から削除(表示から消す)
        '削除後、選択画像(FocusExImage)の変更
        'ひとつ下の画像を選択、一番下だったらひとつ上の画像、最後の画像だったらNothingにする
        '画像が1つもなければ空にする
        '削除画像が一番下ならひとつ上の画像を選択
        'それ以外ならひとつ下の画像を選択
        If CollectionExImage.Count = 0 Then
            FocusExImage = Nothing
        ElseIf i = 0 Then
            FocusExImage = CollectionExImage(i)
        Else
            FocusExImage = CollectionExImage(i - 1)
        End If
    End Sub

End Class


このコードの記事は
WPFとVBでアプリ作る準備その8、コントロールの削除、変数の中身が変化した時に何かの処理をしたい時はPropertyのSetで ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13926863.html




イメージ 1
できればこのXAMLの部分も載せたいんだけど
コピペして記事投稿すると

イメージ 2
記事の投稿が完了しましたって表示されるけど
投稿した記事を見るをクリックすると

イメージ 3
指定された記事が見つかりません。
ってエラーになる
3日くらいかけて5回くらい試して、1時間待ったりしたけど
必ずこのエラーなのでムリデス
<>が混じっているのが良くないのかなあ

WPFとVBでアプリ作る準備その9、XAMLでコントロールのレイアウト

$
0
0
レイアウト

Pixtack紫陽花のレイアウト
イメージ 1
ウィンドウは左右に分けてどちらも固定表示で非表示とかにはできなかった
右側が編集する画像表示
左側は必要な機能を表示させていた
必要じゃないけどあったらいいな機能は別ウィンドウにしていた
メニューバーは無い、ステータスバーはあり
これでほぼ満足していたのでだいたい同じ感じで

イメージ 2
こうなった
ウィンドウ上側にメニューバーとツールバーを置いて
別ウィンドウは無くして左側に表示するようにした
ウィンドウ左側を表示非表示の切り替えできるようにした
山吹色のところが選択中の画像を表示するところでこの部分も
表示非表示の切り替えできるようにしてみた
タブはスクロールバーを表示できるようにした
それ以外は一緒

イメージ 3
表示の切り替えしているところ
理想は左側を切り離して別ウィンドウで表示とか、タブの一部を切り離して別ウィンドウで表示もできることなんだけど、難しくてできそうにないのでこうなった

デザイン画面、XAML
イメージ 4
特別なことは何もしていなくてコントロールを置いただけ

MainWindow
イメージ 5
Class MainWindow
    Private Sub hyouzi11() Handles menu1.Click, button1.Click
        Dim zero As New GridLength(0)

        If gridColumn0.Width = zero Then
            gridColumn0.Width = New GridLength(200)
        Else
            gridColumn0.Width = zero
        End If
    End Sub
    Private Sub hyouzi22() Handles menu2.Click, button2.Click
        If grid2.Height > 0 Then
            grid2.Height = 0
        Else
            grid2.Height = 100
        End If
    End Sub
End Class
ウィンドウ左側の表示非表示の動作は
正確には表示非表示の切り替えじゃなくて
幅や高さを0にして非表示に見せかけているだけ

イメージ 6
左側のGridはWidthを200に指定している
これを0にすれば

イメージ 7
非表示になる
なのでVBのコードの方で
gridColumn0.Width = 0
って書けばいいのかと思ったら少し違っていて
gridColumn0.Width = New GridLength(0)
こうだった
Gridコントロールの中を分割していて、分割の一部の幅や高さを変更するときは
GridLengthってのを使うみたい
イメージ 8
で、これ
幅0だったら200にして
幅0じゃなかったら0にする


イメージ 9
選択画像を表示しようと思っている山吹色のところは
分割していないGridなので

イメージ 10
そのまま高さHeight指定でOK

WPFのコントロールの配置レイアウトはフォームアプリとはかなり違っていて
自由度も高くていろいろできる!
ツールバーの中にメニューアイテム表示とか、ボタンの中で動画表示とか



予定しているテスト
  • 指定した色を透明にする

完了したテスト
  • 画像ファイルドロップで画像表示
  • 画像をマウスドラッグで移動
  • 表示画像のレイヤー間の移動(入れ替え)ZOrder指定
  • 表示画像すべてを画像ファイルとして保存
  • ドラッグ移動時に指定ピクセル数ごとに移動
  • 指定した画像の表示を消す(削除)
  • レイアウト←New

指定色を透明にするは後でいいやってことでテスト終了!


WPFとVBでPixtack紫陽花の後継作成開始

$
0
0
アプリ作成の途中経過
だいたいできたかなあ

複数画像を1枚にするアプリのPixtack紫陽花の後継にしようと思っている


イメージ 1
起動したところ


イメージ 2
エクスプローラなどから画像ファイルを
ドラッグアンドドロップで

イメージ 3
こうなる

イメージ 4
マウスで画像を適当に動かして

保存ボタンで名前を付けて保存ダイアログ表示
イメージ 5
ファイル名を付けて保存をすることができる
ファイルの種類で画像形式を選択できる

イメージ 6
保存された画像

テストの時から付け加えた機能は
クリップボードから画像を貼り付けと
画像を追加するときの設定をいろいろ

イメージ 7
複数ファイルを追加するときの動き

追加される場所は選択画像が基準にしている
けど、これは一番上か下の画像も選べるようにした方がいいって今思った


スライドさせる値はマイナスにもできるけど不具合あり
ってのも今気づいた
イメージ 8
スクロールバーが表示されない
画像をクリックすれば表示されるけど位置がずれる

メニューバーの表示1
イメージ 9
左上のメニューから表示1で、左側の表示非表示が切り替わる

メニューバーの表示2
イメージ 10
同じく表示2で左側の見本画像やボタン類の表示非表示が切り替わる

イメージ 11
赤枠のところは今のところ意味なし

WPFはツールバーにもメニューアイテムを追加できるから
メニューバーは要らないかな、メニューバーもツールバーもあんまり使ったことないからよくわからん


選択画像は目印として左上に縮小表示がキレイじゃない
Windowsフォームアプリでもいろいろモードが選べたから
WPFでも選べるはずと思ったけど方法がわからなくてググって
WPF Imageコントロールの拡大・縮小アルゴリズム変更方法 - Qiita
http://qiita.com/Nuits/items/c23919eb21db3445dadf
ここみたらImageクラスを継承したクラスを作成していて
見るとC#で書いていてOverride、OnRenderの文字がある
OverrideはVBでいうOverridesだろうってことで真似して

HighQualiyImageクラス
Public Class HighQualiyImage
    Inherits Image
    Protected Overrides Sub OnRender(dc As DrawingContext)
        Me.VisualBitmapScalingMode = BitmapScalingMode.HighQuality
        MyBase.OnRender(dc)
    End Sub
End Class
こう書いてみた
キレイモードはFantでこれはHighQualityと同等とヘルプにもあって
見比べたらやっぱり同じだったので
HighQualityのほうが名前的に綺麗になりそうなのでこっちにしたw
Overridesは最近少しわかってきたところだからギリギリだった


あとはXAMLのほうに
イメージ 12
Imageに代わって
local:HighQualiyImageを追加

イメージ 13
綺麗な縮小表示になった
Nuitsさん、ありがとうございました!

ダウンロード

前回の記事
WPFとVBでアプリ作る準備その9、XAMLでコントロールのレイアウト ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13929569.html



WPFとVB.NET、Canvasの中に回転表示したコントロールのドラッグ移動で気づいたこと

$
0
0
RenderTransformやLayoutTransformで
回転(変形)表示したコントロールは見た目と中身が一致しないので扱いが難しい
以下はCanvasパネルに表示した場合だけで確認

イメージ 1
サイズが100x100のThumbコントロールを
プロパティ画面から右に10度回転の指定をすると
RotateTransform Angle = "10"がXAMLの方に挿入される
RenderTransformとRotateTransformで回転角度が指定されたことで
Thumbも回転表示される
でも回転したのは見た目だけで内部的には回転していない


イメージ 2
右に傾けた青色のThumb
赤枠が元の位置とサイズ
緑枠が回転後のThumbが収まる位置とサイズ
赤丸が元の左上の位置を表す

Canvasパネルの中でマウスドラッグでコントロールを移動させる場合は
緑枠の位置とサイズが必要になる

Canvas1の中のThumb1の見た目通りのRect(位置とサイズ)取得
        Dim gt As GeneralTransform = thumb1.TransformToVisual(canvas1)
        Dim s As Size = thumb1.RenderSize
        Dim r As Rect = gt.TransformBounds(New Rect(s))
これが緑枠になる
これはOK
納得

全く予想外だったのが
回転表示したコントロール上でのマウスの動きに対する位置の取得!

ThumbコントロールはDragDeltaイベントで
マウスドラッグの移動距離を簡単に取得できるので
これを使ってどんな値が取得できているのか見てみた
イメージ 3
回転前と回転後のThumbコントロール上で
マウスを移動させた時に取得できる値の変化
表示している数値がマイナスならそれぞれ左と上

回転前では普通に横の値が変化しているけど
右に90度回転した後だと縦の値が変化していて上に移動していることになっている
つまり回転角度に合わせてマウスの位置も変換されているみたい
うーん、これはある意味見た目通りの動きなのかも?

このわかりづらい罠みたいな動きはThumbのDragDeltaイベントだけじゃなくて
その他のコントロールでのMouseMoveイベントでも同じだった

イメージ 6


イメージ 7
Imports System.Windows.Controls.Canvas
Imports System.Windows.Controls.Primitives

Class MainWindow
    Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
        Dim gt1 As GeneralTransform = thumb1.TransformToVisual(canvas1)
        Dim r1 As Rect = gt1.TransformBounds(New Rect(New Size(thumb1.Width, thumb1.Height)))
        'Dim cp As Point = New Point(GetLeft(rb), GetTop(rb))

        Dim rg1 As New RectangleGeometry(r1)
        path1.Data = rg1

        Dim p1 As Point = gt1.Transform(New Point(0, 0))
        SetLeft(elli1, p1.X - (elli1.Width / 2))
        SetTop(elli1, p1.Y - (elli1.Height / 2))

    End Sub

    Private Sub kaiten_ValueChanged(sender As Object, e As RoutedPropertyChangedEventArgs(Of Double)) Handles kaiten.ValueChanged
        Call waku()
    End Sub

    Private Sub kakusyuku_ValueChanged(sender As Object, e As RoutedPropertyChangedEventArgs(Of Double)) Handles kakusyuku.ValueChanged
        Call waku()
    End Sub

    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        Call motonowaku()
    End Sub

    Private Sub waku()
        Dim rt As New RotateTransform(kaiten.Value)
        thumb1.RenderTransform = rt
        Dim st As New ScaleTransform(kakusyuku.Value, kakusyuku.Value)
        thumb1.RenderTransform = st


        Dim tg As New TransformGroup
        Dim tc As New TransformCollection
        tc.Add(rt)
        tc.Add(st)
        tg.Children = tc

        thumb1.RenderTransform = tg 'RenderTransform
        'thumb1.LayoutTransform = rt 'LayoutTransform    
        Dim gt As GeneralTransform = thumb1.TransformToVisual(canvas1)
        Dim s As Size = thumb1.RenderSize
        Dim r As Rect = gt.TransformBounds(New Rect(s))
        Dim rg As New RectangleGeometry(r)
        path1.Data = rg

        '○
        Dim topLeft As Point = gt.Transform(New Point(0, 0))
        SetLeft(elli1, topLeft.X)
        SetTop(elli1, topLeft.Y)

    End Sub
    Private Sub motonowaku()
        Dim r2 As New Rect(GetLeft(thumb1), GetTop(thumb1), thumb1.Width, thumb1.Height)
        Dim rg2 As New RectangleGeometry(r2)
        path2.Data = rg2

    End Sub

    Private Sub rb_DragDelta(sender As Object, e As Primitives.DragDeltaEventArgs) Handles thumb1.DragDelta
        textblock1.Text = $"マウスは横に{e.HorizontalChange:000}、縦に{e.VerticalChange:000}移動"
    End Sub

    Private Sub rb_DragCompleted(sender As Object, e As DragCompletedEventArgs) Handles thumb1.DragCompleted
        Cursor = Cursors.Arrow
        textblock1.Text = $"マウスは横に000、縦に000移動"
    End Sub

    Private Sub rb_DragStarted(sender As Object, e As DragStartedEventArgs) Handles thumb1.DragStarted
        Cursor = Cursors.Hand
    End Sub
End Class








回転させたものをドラッグ移動させると
ウィンドウの外に勢い良くすっ飛んでいく原因を探っていて気づいたけど
1週間くらいかかったYO!
気づいたのはいいけど今度は期待通りの値を取得する方法がわからない
思いついたのが
WPFとVBでアプリ作る準備その2、ControlTemplateの中のControlを取得する ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13906217.html
この時に使ったControlTemplateを使う方法

イメージ 4
XAMLのほうで
Thumb.Template
ControlTemplate
Image
ってThumbのControlTemplateにImageを入れておいてImageだけを回転させれば
Thumbから取得できるマウスの値は期待したものになるし
画像も回転表示できる

中のImageを回転させるためには
対象となるThumbのTemplateの中にあるimage1と名前を付けておいたImageを取得、tがThumb
  'テンプレートの中のコントロールを取得するTemplate.FindName
        Dim img As Image = t.Template.FindName("image1", t)
あとはImageに対してRotateTransformを使って回転させればいい

Imageを30度回転するとき
Dim rt As New RotateTransform(30)
Image.RenderTransform = rt
ふつうにできる
回転後はImageのサイズが変わるけどThumbも自動で変わるので
指定する必要はないみたい?
ただし両方共見た目だけが変化しただけで内部では回転前の位置とサイズ
なのはプロパティのRenderSizeとか見るとわかる



回転後の見た目通りのRect(位置とサイズ)を取得、上の例だと緑枠の取得するには
Imageを対象にする

tがThumb、canvas1がCanvas
        Dim img As Image = t.Template.FindName("image1", t)
        Dim gt As GeneralTransform = img.TransformToVisual(canvas1)
        Dim s As Size = img.RenderSize
        Dim r As Rect = gt.TransformBounds(New Rect(s))


そんなこんなで回転や拡大をできるようになった
イメージ 5

動かすコントロールをImageからImageを中に入れたThumbに変更することになって、大幅書き換えになったけど満足の行くできになった
画像の回転はWindowsフォームアプリのときは結構手間がかかったんだけど
WPFでは実質2行でできるから簡単にできそうだなって思って作り始めたら
思わぬところで時間がかかったw
でももっと作りこんでから気づいた場合は書き直しがもっと大変だろうから
今回気づけてよかった


アプリとソース一式ダウンロード先
ヤフーボックス
Dropbox


WPFとVB.NETで表示した画像をクリックした場所の色を取得はややこしい

$
0
0
WPFで画像をクリックした場所の色を取得

イメージ 2

イメージ 4

イメージ 3
Class MainWindow
    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded

        Dim b As BitmapImage
        Dim path As Uri
        path = New Uri("D:\ブログ用\テスト用画像\TransparentRect.png")
        'path = New Uri("D:\ブログ用\テスト用画像\TransparentRect.jpg")
        'path = New Uri("D:\ブログ用\テスト用画像\TransparentRect.bmp")
        'path = New Uri("D:\ブログ用\テスト用画像\TransparentRect.gif")
        'path = New Uri("D:\ブログ用\テスト用画像\TransparentRect.tiff")
        b = New BitmapImage(path)
        image1.Source = b
        image1.Stretch = Stretch.None
    End Sub

    Private Sub image1_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles image1.MouseLeftButtonDown
        '        [VB.NET][WPF] マウスクリックした箇所のスクリーン座標(絶対座標)を取得する | オールトの雲
        'http://ooltcloud.expressweb.jp/201312/article_08004931.html

        Dim clickPoint As Point = e.GetPosition(image1)
        textblock1.Text = $"Locate={clickPoint.ToString}"

        Dim x As Integer = clickPoint.X
        Dim y As Integer = clickPoint.Y
        Dim b As BitmapSource = image1.Source
        Dim wb As Integer = b.PixelWidth
        Dim hb As Integer = b.PixelHeight
        '画像の端ギリギリをクリックすると画像サイズより大きな値になることがあるのでその時は-1する
        If x >= wb Then x = wb - 1
        If y >= hb Then y = hb - 1
        'Dim col As Color = GetPixel(x, y)
        'Dim col As Color = neko(x, y)
        Dim col As Color = neko2(x, y)
        rectangle1.Fill = New SolidColorBrush(col)
        tbARGB.Text = $"ARGB={col.ToString}"
    End Sub

    '    wpf - Finding specific pixel colors of a BitmapImage - Stack Overflow
    'http://stackoverflow.com/questions/1176910/finding-specific-pixel-colors-of-a-bitmapimage
    '1x1のBitmapを切り出してPixelFormatをBgra32に変換して
    'CopyPixelsでピクセルデータの配列にしてRGBAを取り出す
    'x,yはクリックした位置
    Private Function neko2(x As Integer, y As Integer) As Color
        Dim bmp As BitmapSource = image1.Source '表示画像取得
        Dim cb As New CroppedBitmap(bmp, New Int32Rect(x, y, 1, 1))
        Dim fcb As New FormatConvertedBitmap(cb, PixelFormats.Bgra32, Nothing, 0)
        Dim pixels(3) As Byte 'コピー先の場所作成
        'cb.CopyPixels(pixels, 4, 0)
        fcb.CopyPixels(pixels, 4, 0)
        Dim c As Color ' = Colors.White
        c = Color.FromArgb(pixels(3), pixels(2), pixels(1), pixels(0))
        Return c
    End Function
End Class
余計なコメントや名前が変だけどこんな感じ


Windowsフォームアプリの時は簡単に取得できるGetPixelメソッドが
用意されていたんだけど
WPFには無いみたいで
wpf - Finding specific pixel colors of a BitmapImage - Stack Overflow
http://stackoverflow.com/questions/1176910/finding-specific-pixel-colors-of-a-bitmapimage
ここを参考にして(またしーしゃーぷか!しかも英語)

    Private Function GetPixelColor(x As Integer, y As Integer, bmp as BitmapSource) As Color
        Dim cb As New CroppedBitmap(bmp, New Int32Rect(x, y, 1, 1))
        Dim fcb As New FormatConvertedBitmap(cb, PixelFormats.Bgra32, Nothing, 0)
        Dim pixels(3) As Byte
        fcb.CopyPixels(pixels, 4, 0)
        Dim c As Color
        c = Color.FromArgb(pixels(3), pixels(2), pixels(1), pixels(0))
        Return c
    End Function
xとyがクリックした位置、bmpがクリックした画像
CroppedBitmapでbmpから1x1の大きさの画像を取り出す
これがクリックされたPixelになるので
あとはCopyPixelsで色の値を取り出すだけなんだけど
その前にFormatConvertedBitmapでピクセルフォーマットをBgra32に変換している
これは
イメージ 1
CopyPixelsの引数に必要なBitmapのstrideをいくつにすればいいのかわかりやすくなるから

Strideは
画像のピクセルフォーマットと画像の横幅というか横に並んだピクセルの数
このふたつが関係しているみたいで
ピクセルフォーマットがBgra32で横幅100の画像の時Strideは
4*100=400
になる
今回はクリックした位置の1x1の画像だから横幅1で4*1=4
つまりBgra32なら横幅x4でいいみたい

どんな画像でもピクセルフォーマットがBgra32ならいいけど
いろいろあってそれによってx4の部分が変わるらしいけどよくわかんないので
FormatConvertedBitmapっていう便利なのがあったのでBgra32に統一した
なので画像によっては違う色になるかも?
適当な画像で試した限りでは問題なかった
拡張子でいったらpng,jpeg,bmp,gif,tiffこの辺りは大丈夫だったけど
同じ形式でもいろいろなピクセルフォーマットはあるみたいでよくわからん

コピーした値
イメージ 5
BGRAそれぞれの4つの値が配列にコピーされたところ



画像を表示させているimage1のMouseLeftDownイベントに

    Private Sub image1_MouseLeftDown(sender As Object, e As MouseButtonEventArgs) Handles image1.MouseLeftButtonDown
        Dim clickPoint As Point = e.GetPosition(image1)
        textblock1.Text = $"Locate={clickPoint.ToString}"

        Dim x As Integer = clickPoint.X
        Dim y As Integer = clickPoint.Y
        Dim b As BitmapSource = image1.Source
        Dim wb As Integer = b.PixelWidth
        Dim hb As Integer = b.PixelHeight
        '画像の端ギリギリをクリックすると画像サイズより大きな値になることがあるのでその時は-1する
        If x >= wb Then x = wb - 1
        If y >= hb Then y = hb - 1
        Dim col As Color = GetPixelColor(x, y, b)
        rectangle1.Fill = New SolidColorBrush(col)
        tbARGB.Text = $"ARGB={col.ToString}"
    End Sub
クリックした場所は
マウスイベントのGetPositionで取得できる
けどなんかおかしくて
100x100のピクセルサイズの画像なのに
端ギリギリをクリックした時に101とかが返ってくる時があるので
サイズを超えていた時は-1してからGetPixelColorに渡している


イメージ 6
マウスカーソルの形を変えて色取得モードに
カーソル下の色を常に表示
画像クリックで色確定して色取得モード終了
途中でやめるときはもう一度色取得ボタン押す

WPFではクリックした場所の色取得がこんなに面倒だとは思わなかった
なんでもかんでもWPFのほうがいいってわけでもないんだなあと


複数画像をまとめるのが目的のアプリだし保存するときは画像形式によって
ある程度ピクセルフォーマットも決まるみたいだから
画像読み込み時にピクセルフォーマットをBgra32に変換するようにした
これなら色取得の度にピクセルフォーマットを変換する手間が省ける


Pixtack2nd_20160303.zipダウンロード
これで色取得はできたと思ったけど終わってなかった
この記事の続きは
WPFとVB.NETで表示した画像をクリックした場所の色を取得はややこしい(後編) ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13955791.html







WPFとVB.NETで表示した画像をクリックした場所の色を取得はややこしい(後編)

$
0
0
前回の画像をクリックした場所の色を取得の続き、終わったと思ったら終わってなかった
WPFとVB.NETで表示した画像をクリックした場所の色を取得はややこしい ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13952774.html
のつづき

画像とWindowsのDpiが違うと画像の上にあるマウスカーソルから取得できる位置は見た目の位置と違うので、画像クリックで色を取得するには一工夫

Dpi、ドットパーインチ、1インチに並んでいるピクセルの数
画像ファイルによってDpiの数値は様々
携帯やデジカメは72Dpiとか
Windowsの標準は96Dpiで表示
スキャナで取り込んだものは600Dpiとかいろいろある
WPFで作ったアプリで普通に表示するとDpiが反映されたサイズで表示される

DpiがいくつなのかJpeg画像ならファイルのプロパティで確認できる
イメージ 2

WPFのBitmapImageに画像ファイルを渡してImageのSourceに指定すると
Dpiが考慮された表示になる
100x100ピクセルの画像をWindows標準の96Dpiで表示する時
96Dpiの画像なら96/96*100=100x100ピクセルの大きさで表示される
72Dpiの画像は96/72*100=133.33333x133.33333…
600Dpiは96/600*100=16x16


イメージ 1
どちらも同じ256x192ピクセルの画像
これを表示しているWindowsは標準の96Dpiに設定してあるので
Dpiが96の画像だと1ドットに1ピクセルが表示されるので256x192ピクセルが
そのまま表示される
Dpiが72だと少し引き伸ばされたように表示される

Dpi72、256x192ピクセルの画像を表示した時の
ImageとそのSource(画像)
イメージ 3
引き伸ばされて341.3x256で表示されている
画像をクリックするときは表示されているものをクリックするわけだから
341x256の中のどこか1点ってことになる
でも中の画像は256x192なので変換が必要になる

クリックした場所が(x,y)のとき中の画像の位置(nx,ny)は
nx = 画像のDpi/96*x
ny = 画像のDpi/96*y

クリックした場所が100,156なら
72/96*100=75
72/96*156=117
中の画像の75,117になる


別の方法
画像を読み込むときにDpiを96に変更して表示する
これなら見た目通りの位置とサイズになるので変換の必要がなくなる
でもこのDpiを変更ってのが簡単じゃない

BitmapとDpiの数値を渡すとDpi変更したBitmapを返す
'BitmapSourceのDpi変更、対応ピクセルフォーマットはBgra32だけ
Private Function ChangeDpi(b As BitmapSource, Optional x As Double = 96,
                               Optional y As Double = 96) As CachedBitmap
Dim stride As Integer = b.PixelWidth * 4
Dim pixels(b.PixelHeight * stride - 1) As Byte
b.CopyPixels(pixels, stride, 0)
       'BitmapSource.Createから作成したBitmapはCachedBitmapになるみたい
Dim cb As CachedBitmap = BitmapSource.Create(
b.PixelWidth, b.PixelHeight, x, y, PixelFormats.Bgra32, Nothing, pixels, stride)
        Return cb
    End Function
これで変更できるけどもっといい方法があるはず
渡されたBitmapをCopyPixelsメソッドでByte配列にバラバラにして
それを元に新たにBitmapSourceを作って返しているだけ
BitmapSourceのCreateでDpiを指定できるのでそれを利用している

ChangeDpiはBgra32しか対応していないので
読み込んだ画像をBgra32に変換しておく必要がある
これはFormatConvertedBitmapっていう便利なのがあるので
FormatConvertedBitmap(BitmapSource, PixelFormats.Bgra32, Nothing, 0)
これで変換できる

イメージ 4
上が座標変換で
下がDpi変換

次回は指定した色を透明にするになる予定




WPFとVB.NETで画像の中の特定の色を透明にする

$
0
0
前回でマウスカーソルの位置の色取得はできたので
今度は画像からその色を透明にする

イメージ 1
取得した色を左側に表示、画像クリックでその色を透明にする

イメージ 6

イメージ 7

Class MainWindow

    'マウス移動のとき、カーソルの位置を取得して表示する
    '画像のDpiとOSで指定しているDpiが違うとGetPositionで得られるマウスの位置と
    '見た目の位置が違ってくるので修正する必要がある
    Private Sub image1_MouseMove(sender As Object, e As MouseEventArgs) Handles image1.MouseMove
        Dim img As Image = DirectCast(sender, Image)

        'GetPositionでカーソルの位置を取得
        Dim p As Point = e.GetPosition(img)
        tbPoint1.Text = p.ToString

        Dim b As BitmapSource = img.Source
        Dim w As Integer = b.PixelWidth
        Dim h As Integer = b.PixelHeight

        '96はWindows標準のDpi
        Dim sa As Double = b.DpiX / 96
        'Dpiの違いを修正
        Dim x As Integer = p.X * sa
        Dim y As Integer = p.Y * sa
        '画像の端のときにたまに大きな値が返ってくるので-1している
        If x >= w Then x = w - 1
        If y >= h Then y = h - 1
        'クリックした場所の色を取得
        Dim c As Color = GetPixelColor(x, y, b)
        rectColor1.Fill = New SolidColorBrush(c)
        'タグプロパティに色を入れておく
        rectColor1.Tag = c
    End Sub

    'Image2の画像はDpiを96に変更してから表示したので修正なし
    Private Sub image2_MouseMove(sender As Object, e As MouseEventArgs) Handles image2.MouseMove
        'マウスカーソルの下の色を取得して表示
        Dim img As Image = DirectCast(sender, Image)
        Dim p As Point = e.GetPosition(img)
        tbPoint2.Text = p.ToString

        Dim b As BitmapSource = img.Source
        Dim w As Integer = b.PixelWidth
        Dim h As Integer = b.PixelHeight

        Dim x As Integer = p.X
        Dim y As Integer = p.Y
        If x >= w Then x = w - 1
        If y >= h Then y = h - 1
        Dim c As Color = GetPixelColor(x, y, b)
        rectColor2.Fill = New SolidColorBrush(c)

        rectColor2.Tag = c
    End Sub

    'image1左クリックのとき
    Private Sub image1_PreviewMouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles image1.PreviewMouseLeftButtonDown
        Dim img As Image = DirectCast(sender, Image)
        img.Source = transparent(img.Source, rectColor1.Tag)
    End Sub
    'image2
    Private Sub image2_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles image2.MouseLeftButtonDown
        Dim img As Image = DirectCast(sender, Image)
        img.Source = transparent(img.Source, rectColor2.Tag)
    End Sub

    '指定した色を透明にする
    Private Function transparent(b As BitmapSource, tpColor As Color) As BitmapSource
        'マウスカーソルの下の色と同じ色を画像から探して見つかったら透明にする
        Dim ptr As Integer
        Dim pixColor As Color
        Dim w As Integer = b.PixelWidth
        Dim h As Integer = b.PixelHeight
        Dim stride As Integer = 4 * w '1行の情報量?Bgra32の1ピクセルは4Byteだから4Bytex横ピクセル数?
        Dim cb As New FormatConvertedBitmap(b, PixelFormats.Bgra32, Nothing, 0) 'Bgra32に変換
        Dim pix(h * stride - 1) As Byte '色情報を入れる配列作成、縦ピクセル数x1行の情報量が全体の情報量
        cb.CopyPixels(pix, stride, 0) '配列に画像の色情報をコピー
        '同じ色があるか1ピクセルごとに調べて見つかったらアルファ値を0(透明)にする
        For y As Integer = 0 To h - 1
            For x As Integer = 0 To w - 1
                ptr = y * stride + (x * 4)
                pixColor = Color.FromArgb(pix(ptr + 3), pix(ptr + 2), pix(ptr + 1), pix(ptr))
                If pixColor = tpColor Then
                    pix(ptr + 3) = 0
                End If
            Next
        Next
        '書き換えが終わった配列からBitmapを作成して返す
        Dim dpi As Double = b.DpiX
        Dim bs As BitmapSource = BitmapSource.Create(w, h, dpi, dpi, PixelFormats.Bgra32, Nothing, pix, stride)
        Return bs
    End Function

    '起動時に画像を表示
    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        Dim path As String
        'path = "D:\ブログ用\テスト用画像\TransparentRect.png"
        'path = "D:\ブログ用\Pixtack2nd\2x2nomoto.png"
        'path = "D:\ブログ用\チェック用2\NEC_3962_2015_12_01_午後わてん.jpg"
        'path = "D:\ブログ用\テスト用画像\NEC_3961_2015_12_01_午後わてん_.jpg"
        'path = "D:\ブログ用\Pixtack2nd\64x64_Dpi200.png"
        path = "D:\ブログ用\テスト用画像\collection_1_dpi72.png"
        Dim bi As New BitmapImage(New Uri(path))

        'BitmapImageに画像を読み込むとDpiが微妙に変化する
        '96なら95.999、100なら100.0139とかになってしまうので修正
        'Integerに入れると近い整数値になるみたい
        Dim dpi As Integer = bi.DpiX
        Dim cb As CachedBitmap = ChangeDpi(bi, dpi, dpi)
        'ピクセルフォーマットをBgra32に変更

        Dim fcb As New FormatConvertedBitmap(cb, PixelFormats.Bgra32, Nothing, 0)
        image1.Source = fcb 'bi

        'BitmapのDpiを96に変更する
        'Dim cb As CachedBitmap = ChangeDpi(fcb)
        cb = ChangeDpi(fcb)
        image2.Source = cb
    End Sub

    ' BitmapSourceのDpi変更、対応ピクセルフォーマットはBgra32だけ
    Private Function ChangeDpi(b As BitmapSource, Optional x As Double = 96,
                               Optional y As Double = 96) As CachedBitmap
        Dim stride As Integer = b.PixelWidth * 4
        Dim pixels(b.PixelHeight * stride - 1) As Byte
        b.CopyPixels(pixels, stride, 0)
        'BitmapSource.Createから作成したBitmapはCachedBitmapになるみたい
        Dim cb As CachedBitmap = BitmapSource.Create(
            b.PixelWidth, b.PixelHeight, x, y, PixelFormats.Bgra32, Nothing, pixels, stride)
        Return cb
    End Function

    ''' <summary>
    ''' 画像の指定座標の色を返す
    ''' </summary>
    ''' <param name="x">横</param>
    ''' <param name="y">縦</param>
    ''' <param name="b">画像</param>
    ''' <returns></returns>
    Private Function GetPixelColor(x As Integer, y As Integer, b As BitmapSource) As Color
        Dim croppedB As New CroppedBitmap(b, New Int32Rect(x, y, 1, 1))
        Dim cb As New FormatConvertedBitmap(croppedB, PixelFormats.Pbgra32, Nothing, 100)
        Dim pixels(3) As Byte
        croppedB.CopyPixels(pixels, 4, 0)
        Dim c As Color = Color.FromArgb(pixels(3), pixels(2), pixels(1), pixels(0))
        Return c

    End Function
End Class
前回の続きからなのでDpi関係とマウスカーソルの位置の色取得も入っている

前回でも使っていた
BitmapResourceのCopyPixels
これでコピーした色情報を1ピクセルごとに同じ色か判定して
透明に塗り替えていくだけ

画像のBitmapSourceと透明にしたい色を渡すと塗り替えたBitmapSourceを返す
イメージ 2
ピクセルフォーマットはBgra32固定

Bgra32の
赤色(ARGB=255,255,0,0)
A:透明度
R:赤
G:緑
B:青

赤色(ARGB=255,255,0,0)の1ピクセルの画像でBgra32のときCopyPixelsを使って色情報をbyte配列にすると
0,0,255,255と逆順になった配列が返ってくる
このCopyPixelsはWindowsフォームアプリの時に使っていた
System.Runtime.InteropServices.Marshal.Copy
これによく似ている

Bitmapビジュアライザ、BitmapData、LockBits、配列に入れた時の順番 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/12716715.html


今回は順番は関係ないけど配列に入るピクセルの順番は左上から右の順番になる
1ピクセルごとに指定した色と同じかどうかを判定して同じだったらAを0に書き換えれば透明になる
配列の中の場所指定
1ピクセル目は0,1,2,3
2ピクセル目は4,5,6,7
3ピクセル目は8,9,10,11
なので4の倍数にそれぞれ+0,+1,+2,+3
これで指定できる
イメージ 3
hが画像の縦のピクセル数、wが画像の横のピクセル数
pixが色情報の配列
Strideは配列の1行分の要素数
ptrが各ピクセルの先頭Bの場所で
y*Strideで今の行、これにx*4(4の倍数)を足したもの
ptrにそれぞれ0,1,2,3を足したのが1ピクセルのBGRAそれぞれの情報になる
Color.FromArgbで色を作って
tpColorは透明にしたい色
これと比べて同じならA(基準+3)=0

書き換えが終わったら配列からBitmapSource画像作成
イメージ 4
これでできあがり

イメージ 5
BitmapSource.Createで作成されるのはBitmapSourceじゃなくて
CachedBitmapってなっている、よくわからん


次期Pixtack紫陽花にも付けてみた
イメージ 8
色の取得は半透明の色が重なった色で取得されるけど
透明にするのは選択画像にその色があればって条件なので
重なってできた色がその画像に無ければ透明にならない
黄色の画像のところがそれ

画像の加工ができるようになるとアンドゥ・リドゥの機能が欲しくなるけど
ちょっと調べた限りではかなり難しそう、正確にはムリそう

Pixtack2nd_20160305.zipのダウンロード
Dropbox
ヤフーボックス


WPF、XAMLでRadioButton(ラジオボタン)の外観変更

$
0
0
WPFのラジオボタンの外観変更

イメージ 1
この⦿がラジオボタン
Windows 10以外だとまた違うのかもしれないけど
WPF、XAMLで用意されているラジオボタンをそのまま表示するとこのデザイン状態

同じWindows 10でもWindowsFormアプリとは違うデザインになっている
イメージ 2
右がWindowsFormアプリのPixtack紫陽花
WPFのよりWindowsFormのほうが見やすいなあと
WPFというかXAMLのほうが正確かな
XAMLはWindowsFormに比べてコントロールの外観の変更が容易…と言われている
だけどわからんw
コントロールの動きと連動させるのがわかんなくて
ラジオボタンの場合だとチェックの有無で切り替えるってところ
ググッて
チェックマークにイメージを表示する | HIRO's.NET Blog
http://blog.hiros-dot.net/?page_id=4439
おおっありました、ここ見て

イメージ 3
ヤフーブログはXAMLを貼り付けるとエラーになるので画像だけなのがつらい
これを起動すると

イメージ 4
指定なしがそのまま表示のもので
カスタムスタイルはStyleとControlTemplateを使って外観変更
ControlTemplateはControlTemplateだけ使って外観変更したもの
変更したものはフォントサイズによって○の大きさも変化するようにしてみた

説明されている方法ではStyleとControlTemplateを使っていたけど
Styleってよくわかんないから
ControlTemplateだけでもできないかなって試したのがこれ
イメージ 5
これでも期待通りに動いた!
BulletDecoratorの中にBulletDecorator.Bulletって書いて
その中に表示したいものを書けばいいみたい
46行目~55行目
今回はLightGrayの枠の付いた白丸を描いて
その上にOrangeRedの枠の付いた白丸を描いてCheckMark3と名前をつけて
その上にチェック用の小さいOrangeの丸を描いてCheckMark2と名前をつけた
表示する文字がContentPresenterってところで
Grid.Column="1"ってあるからBulletDecoratorってのはGridで仕切られているみたい?
よくわからんけどこれで○の右隣りに指定した文字が表示される

57行目~
ラジオボタンのプロパティIsCheckedがFalseになったチェック無し時の動き
CheckMark2とCheckMark3と名前をつけておいたコントロールを非表示にする



名前がまだ決まらないアプリ
イメージ 7
一部のラジオボタンの外観変更してみた

イメージ 8
初期のラジオボタンは下層、昇順、降順
上層と下層のところで比べてみると
初期のラジオボタンは文字と○の高さがずれているのがわかる


WindowsXPの頃のWindowsForm
イメージ 6
WindowsFormよりXAMLやWPFのほうが新しいはずなのに
XAMLの標準のコントロールの外観てWindowsXPの頃のWindowsFormにそっくり

イメージ 10
Windows 10でWindowsForm
Windows 8もほとんど一緒でXPから8に変えた時には同じアプリでも
見た目が結構変わって見やすくなったなあって喜んでいた

イメージ 9
Windows 10でWPF、XAML
新しいはずなのにXPっぽいw
見た目の変更は容易にしておいたから気に入らなかったら自分で変えてねって
ことなのかしらねえ



名前が決まらない(複数画像を1枚にまとめる)アプリの更新履歴

$
0
0
イメージ 1

カーソルキーで画像を移動できるようにした
ショートカットキーいくつか追加
ctrl+s名前を付けて保存
PageUp選択画像を上層へ移動
PageDown選択画像を下層へ移動
F4選択画像を削除

メニューバーを使えるようにした
イメージ 2

イメージ 3

イメージ 4

イメージ 5

イメージ 6

2016/03/09
ラジオボタンの外観変更

2016/03/10
ボタンの外観

2016/03/11
アプリの設定保存、ウィンドウの位置とサイズ

2016/03/13
ファイル保存時にアプリ名を入れるようにした

2016/03/15
拡大縮小でx,yを個別指定と同期指定を選べるようにした
相対指定のほかに絶対指定もできるようにしてみた
ユーザーコントロールでNumeric作成、表示小数点桁数指定、最大値最小値指定
イメージ 7
画像の変形の拡大縮小で縦横同期にチェック入れで縦横どちらからの数値変更で同期

イメージ 8
縦横別々に拡大率設定


2016/03/17
変形の傾斜をできるようにした
拡大縮小での絶対値指定を手直し、変形後のものに対応した
イメージ 9
横方向、縦方向への傾斜

ピクセル数指定で拡大縮小
イメージ 10
Getボタンで選択画像のサイズを取得できる

イメージ 11
幅と高さを指定してSetボタンで変形(拡大縮小)
拡大率の数値も変化する

横幅優先と高さ優先のラジオボタンは縦横同期にチェックが入っているときに有効
イメージ 12
横幅優先にチェックが入っていると高さの指定は無視されて
アスペクト比を保つように拡大縮小される
上の場合は正方形の画像だったので幅と同じになった

イメージ 13
さっきの傾斜させた画像、幅148.27、高さ187.96を横幅優先で幅109に変形の場合

イメージ 14
結果

2016/03/18
ヌメリックNumericUpDownの手直し、数値と+-以外は入力できないようにした



2016/03/12だったかな
WindowsUpdateしたらなぜかWindowsアップグレードが始まった、アップデートじゃなくてアップグレード、ナンデ?
2時間ちょっとかかって終わったけどWindows 10からWindows 11になるわけでもなく
イメージ 15
白色だったタスクバーの右クリックメニューが黒くなっていたくらいで
何が変わったのかわからなかったけど

謎のアップグレードの前と後
イメージ 16
アプリの方では何も変更していないのに見た目が今っぽくなっていた!
嬉しいんだけど
この前ボタンやラジオボタンの外観をControlTemplateやStyleを使って変更していたのは何だったのかと

ダウンロード

制作環境
Windows 10、Visual Studio Community 2015、WPF、VB
なのでWindows Vista以降なら動くはず







WPFとVB.NETでカラーピッカーその1

$
0
0
イメージ 10
指定した色を透明にするときに色を直接選びたい時があるけど

WPFにはカラーダイアログが無い
イメージ 1
参照を追加すればいつものWindowsForm用の↑が使えるみたいなんだけど
別のを作ってみたい!ってことで作り始めた

イメージ 2
今回のは最低限のもの

デザイン画面、XAML
イメージ 3


VBコード

Class MainWindow
    Private Const MaxSV As Integer = 100 'SとVの最大値、100%表示なら100、詳細なら255指定


    Public Structure HSV
        Public H As Double
        Public S As Double
        Public V As Double
    End Structure

    Private Function RGBtoHSV(c As Color) As HSV
        Dim h, s, v As Double

        Dim r As Integer = c.R
        Dim g As Integer = c.G
        Dim b As Integer = c.B
        Dim min As Integer = Math.Min(b, Math.Min(r, g))
        Dim max As Integer = Math.Max(b, Math.Max(r, g))

        If max = min Then
            h = 0
        ElseIf max = r Then
            h = 60 * ((g - b) / (max - min))
        ElseIf max = g Then
            h = 60 * ((b - r) / (max - min)) + 120
        ElseIf max = b Then
            h = 60 * ((r - g) / (max - min)) + 240
        End If

        'hがマイナスなら360足す、RとBが同じ値だとマイナスになることがある
        If h < 0 Then h += 360

        ''100%表示の時のSV
        's = ((max - min) / max) * 100
        'v = (max / 255) * 100

        ''最大値が255表示の時のSV
        's = (max - min) / max * 255
        'v = max

        'Const設定のSV
        s = (max - min) / max * MaxSV
        v = (max / 255) * MaxSV

        Dim hsv As New HSV
        With hsv
            .H = h
            .S = s
            .V = v
        End With
        Return hsv
    End Function


    '    HSV色空間 - Wikipedia
    ''' <summary>
    ''' HSVをRGB(Color)に変換する、RGBは小数点付きの0-255で返す
    ''' </summary>
    ''' <param name="hsv"></param>
    ''' <returns></returns>
    Private Function HSV2RGB(hsv As HSV) As Color
        Dim h As Double = hsv.H / 360
        Dim s As Double = hsv.S / MaxSV ' 255 ' 100
        Dim v As Double = hsv.V / MaxSV ' 255 ' 100
        Dim r As Double = v
        Dim g As Double = v
        Dim b As Double = v
        Dim neko As Double
        If s > 0 Then
            h *= 6
            Dim i As Integer = Math.Floor(h)
            Dim f As Double = h - i
            Select Case i
                Case 0
                    g *= 1 - s * (1 - f)
                    b *= 1 - s
                Case 1
                    r *= 1 - s * f
                    b *= 1 - s
                Case 2
                    r *= 1 - s
                    b *= 1 - s * (1 - f)
                Case 3
                    r *= 1 - s
                    g *= 1 - s * f
                Case 4
                    neko = r * (1 - s * (1 - f))
                    r *= 1 - s * (1 - f)
                    g *= 1 - s
                Case 5
                    g *= 1 - s
                    b *= 1 - s * f
            End Select

        End If

        r *= 255
        g *= 255
        b *= 255
        Dim col As Color = Color.FromRgb(r, g, b)
        Return col

    End Function

    Private Sub AddHueBar()
        '色相のバー作成
        Dim w As Integer = imgHue.Width
        Dim h As Integer = 360
        Dim wb As New WriteableBitmap(w, h, 96, 96, PixelFormats.Bgra32, Nothing)
        Dim stride As Integer = wb.BackBufferStride
        Dim pixels(h * stride * w - 1) As Byte
        Dim p As Integer
        wb.CopyPixels(pixels, stride, 0)

        Dim col As Color
        Dim hsv As New HSV
        With hsv
            .H = 0
            .S = MaxSV ' 255
            .V = MaxSV ' 255
        End With

        For y As Integer = 0 To h - 1
            hsv.H = y
            col = HSV2RGB(hsv)
            For x As Integer = 0 To w - 1
                p = y * stride + (x * 4)
                pixels(p + 0) = col.B
                pixels(p + 1) = col.G
                pixels(p + 2) = col.R
                pixels(p + 3) = 255
            Next
        Next

        Dim sourceRect As Int32Rect = New Int32Rect(0, 0, w, h)
        wb.WritePixels(sourceRect, pixels, stride, 0)
        imgHue.Source = wb
    End Sub

    'sv画像
    Private Sub ChangeImageSV(hue As Double)
        Dim w As Integer = MaxSV + 1 '100%表示なら0から100なので101ピクセル必要、255表示なら256ピクセル必要なので+1
        Dim h As Integer = MaxSV + 1 ' 255 'imgHue.Height
        Dim wb As New WriteableBitmap(w, h, 96, 96, PixelFormats.Bgra32, Nothing)
        Dim stride As Integer = wb.BackBufferStride
        Dim pixels(h * stride * w - 1) As Byte
        Dim p As Integer
        wb.CopyPixels(pixels, stride, 0)
        Dim hsv As New HSV
        With hsv
            .H = hue
            .S = MaxSV ' 255
            .V = MaxSV ' 255
        End With
        Dim col As Color
        For y As Integer = 0 To h - 1
            hsv.V = MaxSV - y ' 255 - y
            For x As Integer = 0 To w - 1
                hsv.S = x
                col = HSV2RGB(hsv)
                p = y * stride + (x * 4)
                pixels(p + 0) = col.B
                pixels(p + 1) = col.G
                pixels(p + 2) = col.R
                pixels(p + 3) = 255
            Next
        Next

        Dim sourceRect As Int32Rect = New Int32Rect(0, 0, w, h)
        wb.WritePixels(sourceRect, pixels, stride, 0)
        imgSV.Source = wb
    End Sub

    ''' <summary>
    ''' 画像(BitmapSource)の指定座標の色を取得
    ''' </summary>
    ''' <param name="p">座標</param>
    ''' <param name="bmp">画像</param>
    ''' <returns></returns>
    Private Function GetPixelColor(p As Point, bmp As BitmapSource) As Color
        Dim w As Integer = bmp.PixelWidth
        Dim h As Integer = bmp.PixelHeight
        Dim x As Integer = Math.Floor(p.X)
        Dim y As Integer = Math.Floor(p.Y)

        If x >= w Then x = w - 1
        If y >= h Then y = h - 1

        Dim cb As New CroppedBitmap(bmp, New Int32Rect(x, y, 1, 1))
        Dim fcb As New FormatConvertedBitmap(cb, PixelFormats.Bgra32, Nothing, 0)
        Dim pixels(3) As Byte 'コピー先の場所作成
        fcb.CopyPixels(pixels, 4, 0)
        Return Color.FromArgb(pixels(3), pixels(2), pixels(1), pixels(0))
    End Function




    Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
        '色相バー
        Call AddHueBar()

        '初期SV画像作成、色相は0(赤)で作成
        Call ChangeImageSV(0)
        imgSV.Width = MaxSV + 1 '100%表示なら0から100なので101ピクセル必要、255表示なら256ピクセル必要なので+1
        imgSV.Height = MaxSV + 1

    End Sub

    Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
        AddHandler sldHue.ValueChanged, AddressOf sldHue_ValueChanged
    End Sub


    '色相スライダー変化でSV画像更新
    Private Sub sldHue_ValueChanged(sender As Object, e As RoutedPropertyChangedEventArgs(Of Double))
        Call ChangeImageSV(e.NewValue)
    End Sub

    'SV画像クリック
    Private Sub imgSV_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles imgSV.MouseLeftButtonDown
        Dim clickPoint As Point = e.GetPosition(imgSV)
        Dim col As Color = GetPixelColor(clickPoint, imgSV.Source)
        rectMihon.Fill = New SolidColorBrush(col)
        tbkRGB.Text = $"ARGB = {col.ToString}"

        tbkRGB2.Text = $"ARGB = {col.A:000},{col.R:000},{col.G:000},{col.B:000}"
    End Sub

End Class



イメージ 4

↑をどこで何をしているかを書き加えたのが↓
イメージ 5

RGBとHSVの変換式は
        HSV色空間 - Wikipedia
https://ja.wikipedia.org/wiki/HSV%E8%89%B2%E7%A9%BA%E9%96%93
ここのコピペ

イメージ 6
3行目のMaxSVがSとVの最大値指定でここを100なら0%から100%表示
255にすると0から255で色を表現する
255にして起動すると↓
イメージ 7
クリックする画像も大きくなる256x256
100よりこっちのほうが詳細だけど必要はないのかな
右の色相スライダーを動かした時にこの画像を書き換えるから
大きな画像だと処理が大変ってのもある

ピクセルごとに色を指定して画像を作成するのは前回の↓この記事とほぼ同じ
WPFとVB.NETで画像の中の特定の色を透明にする ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13957183.html

WriteableBitmap作成
CopyPixelsで色のByte配列を取得
Byte配列の中を書き換える(色の指定)
WritePixelsでByte配列を上書き
っていう流れ
これ書いてて思ったけどCopyPixelsを使ってByte配列を取得しなくても
直接Byte配列を作成してもできそうな気がする


画像をクリックした場所の色取得は
WPFとVB.NETで表示した画像をクリックした場所の色を取得はややこしい ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/13952774.html
この記事の時とほとんど同じ

イメージ 9
クリックした座標取得は
クリックイベントでGetPositionで取得して
座標と画像を↓に渡す
イメージ 8





この記事の続き
WPFとVB.NETでカラーピッカーその2、HSVとRGBで色指定 ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14025927.html



Viewing all 420 articles
Browse latest View live