C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

コントロールを自然に動かすには??

[トピック内 16 記事 (1 - 16 表示)]  << 0 >>

■88470 / inTopicNo.1)  コントロールを自然に動かすには??
  
□投稿者/ 熊さん (1回)-(2018/09/02(Sun) 22:41:45)

分類:[.NET 全般] 

NumericUpDownを使って、
PictureBoxのサイズを動かしたいと考えています。

NumericUpDown1.Value
に0〜1までの値が入っているとして、


    Private Sub NumericUpDown1_ValueChanged(sender As Object, e As EventArgs) Handles NumericUpDown1.ValueChanged

        PictureBox1.Top = 100 + CInt(500 * (1 - NumericUpDown1.Value) * 0.5)
        PictureBox1.Height = CInt(500 * NumericUpDown1.Value)

    End Sub

のようにして、
サイズを変更するコードを書きました。
しかし、この方法だと、
PictureBoxの位置をずらした後に、サイズを変更するコードとなっているため、
一瞬だけ、PictureBoxが移動したのが見えてしまい、
非常に不格好です。

そのため、


        Me.SuspendLayout()

        PictureBox1.Top = 100 + CInt(500 * (1 - NumericUpDown1.Value) * 0.5)
        PictureBox1.Height = CInt(500 * NumericUpDown1.Value)

        Me.ResumeLayout(False)

のように、コントロールの外観を変更している間
フォームが描画されないようにしてみたのですが、
やはり、変化はなく、PictureBoxの移動が目に見えてしまいます。

        Me.ResumeLayout(False)

の前に
Thread.Sleep(1000)
を入れて、動作を確認してみたのですが、
どうも
 Me.SuspendLayout()
がうまく機能しておらず、
Me.ResumeLayout(False)
に入る前に、描画されてしまっているように思います。

一体、どうすれば、コントロールの動きを同時に行うことができますか?


引用返信 編集キー/
■88472 / inTopicNo.2)  Re[1]: コントロールを自然に動かすには??
□投稿者/ Azulean (993回)-(2018/09/02(Sun) 23:10:14)
Bounds プロパティで位置とサイズを一気に変えても見た目は変わらず…ですかね?
引用返信 編集キー/
■88473 / inTopicNo.3)  Re[1]: コントロールを自然に動かすには??
□投稿者/ 通りすがり (6回)-(2018/09/02(Sun) 23:42:38)
No88470 (熊さん さん) に返信

多分Boundsで用は足りると思うけど

> Me.SuspendLayout()

はコンテナのレイアウトロジックを停止させるものなのでコントロールの描画を止めるなら
https://dobon.net/vb/dotnet/control/beginupdate.html
あたりを参考に
引用返信 編集キー/
■88502 / inTopicNo.4)  Re[2]: コントロールを自然に動かすには??
□投稿者/ 熊さん (2回)-(2018/09/04(Tue) 12:18:39)

ありがとうございます。

PictureBoxの枠線や画像に関してはBoundsでうまくいきました。

あと、PictureBoxの上に


Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles PictureBox1.Paint

Dim g As Graphics = e.Graphics
g.DrawLine(・・・)

End Sub

のようにしてラインを書いているので、ボックスのサイズ変更と同時に位置を調整する必要があります。

PictureBox1.SetBounds(・・・)

’ライン位置を変更するコード

PictureBox1.Refresh()

とやってみたのですが、
一瞬だけラインが動いてしまいます。

BeginUpdate、EndUpdateメソッドを使ってみたのですが、
なぜか、PictureBoxのサイズを変更すると、元あった線が消えずに残るため
二重3重4重に枠線が表れてしまいます。
PictureBoxはこれらのメソッドに対応していないのでしょうか?

ちなみに
''' コントロールの再描画を停止させる
Public Shared Sub BeginControlUpdate(control As Control)

''' コントロールの再描画を再開させる
Public Shared Sub EndControlUpdate(control As Control)

とありますが、停止させる方がEndで
再開させる方がBeginだと思うのですが、
Webページの誤植でしょうか?



引用返信 編集キー/
■88506 / inTopicNo.5)  Re[3]: コントロールを自然に動かすには??
□投稿者/ 魔界の仮面弁士 (1810回)-(2018/09/04(Tue) 13:59:58)
No88502 (熊さん さん) に返信
> なぜか、PictureBoxのサイズを変更すると、元あった線が消えずに残るため

PictureBox 自身の事だとしたら、PictureBox1.Invalidate() を呼べば OK かと。

PictureBox 自身ではなく、その親コントロールに PictureBox の残像が残るという状況なら、
親の OnPaintBackground メソッドをオーバライドして握りつぶすと改善することがあります。
引用返信 編集キー/
■88540 / inTopicNo.6)  Re[4]: コントロールを自然に動かすには??
□投稿者/ 熊さん (3回)-(2018/09/06(Thu) 08:58:55)
PictureBox1.Invalidate() を呼んでみましたがやはり残像が消えずに残ってしまいます。

Protected Overrides Sub OnPaintBackground(ByVal pevent As System.Windows.Forms.PaintEventArgs)
' 何もしない
End Sub

を試してみたところ、残像が消えないだけでなく
フォームが真っ黒になってしまうのですが・・・
引用返信 編集キー/
■88541 / inTopicNo.7)  Re[5]: コントロールを自然に動かすには??
□投稿者/ 魔界の仮面弁士 (1818回)-(2018/09/06(Thu) 10:21:25)
2018/09/06(Thu) 10:23:05 編集(投稿者)

No88540 (熊さん さん) に返信
> PictureBox1.Invalidate() を呼んでみましたがやはり残像が消えずに残ってしまいます。

No88506 の再確認になりますが、ここでいう「残像」というのは
 Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles PictureBox1.Paint
で描かれた内容が残ってしまう、ということでよろしいでしょうか。

それとも、PictureBox1 の外側(それを載せている Panel や Form 等)に対して
移動前の位置に残像として残ってしまっている、ということなのでしょうか。


後者だとしたら、親側を Invalidate あるいは Refresh するようにしてみてください。

前者については、PictureBox1 自身を Invalidate あるいは Refresh しているので
PictureBox1 の Paint イベントが誘発されるはずなのですが…それでも
残像として残るようであれば、Paint が呼ばれた時点で、
e.Grapchis の Clear メソッドを呼び出すようにしてみては如何でしょう。



あと、フォーム上(というか UI スレッド)からは
Thread.Sleep(1000) を呼ばないようにしましょう。
引用返信 編集キー/
■88543 / inTopicNo.8)  Re[5]: コントロールを自然に動かすには??
□投稿者/ 魔界の仮面弁士 (1820回)-(2018/09/06(Thu) 14:25:18)
No88540 (熊さん さん) に返信
> PictureBox1.Invalidate() を呼んでみましたがやはり残像が消えずに残ってしまいます。

現象を再現可能なサンプルを提示することはできますか?


とりあえず当方でも、
 PictureBox1.SetBounds(-1, y, -1, h, BoundsSpecified.Y Or BoundsSpecified.Height)
および
 PictureBox1.Bounds = New Rectangle(PictureBox1.Left, y, PictureBox1.Width, h)
の 2 パターンで実験コードを書いてみましたが「残像」を確認できませんでした。

※検証環境:.NET Framwork 4.7.2 / VS2017 15.8.2 / Win10 ver1803


Option Strict On
Partial Public Class Form1
    Inherits Form
    Private WithEvents NumericUpDown1 As New NumericUpDown()
    Private WithEvents PictureBox1 As New PictureBox()
    Public Sub New()
        '説明のため、今回はデザイン時設定をコードで記載しています
        'InitializeComponent()

        NumericUpDown1.BeginInit()
        NumericUpDown1.Tag = "Initializing"
        NumericUpDown1.TextAlign = HorizontalAlignment.Right
        NumericUpDown1.DecimalPlaces = 2
        NumericUpDown1.Increment = 0.01D
        NumericUpDown1.Minimum = 0.2D
        NumericUpDown1.Value = 1D
        NumericUpDown1.Maximum = 1.2D
        NumericUpDown1.Font = New Font("Courier New", 21.0F)
        PictureBox1.BackColor = Color.White
        PictureBox1.BorderStyle = BorderStyle.FixedSingle
        PictureBox1.SetBounds(50, 175, 250, 500)
        PictureBox1.BackgroundImage = Icon.ToBitmap()
        PictureBox1.BackgroundImageLayout = ImageLayout.Zoom
        Controls.Add(NumericUpDown1)
        Controls.Add(PictureBox1)
        NumericUpDown1.EndInit()
        NumericUpDown1.Tag = Nothing
        Me.Size = New Size(400, 650)

        '初期化処理中に ValueChanged が処理されないよう、イベントを手動で割り当てている
        ResizePicture()
        AddHandler NumericUpDown1.ValueChanged, AddressOf NumericUpDown1_ValueChanged
    End Sub

    Private Sub ResizePicture()
        'PictureBox1.Top = 100 + CInt(500 * (1 - NumericUpDown1.Value) * 0.5)
        'PictureBox1.Height = CInt(500 * NumericUpDown1.Value)
        Dim y As Integer = 100 + CInt(250D * (1D - NumericUpDown1.Value))
        Dim h As Integer = CInt(500 * NumericUpDown1.Value)

        'PictureBox1.SetBounds(-1, y, -1, h, BoundsSpecified.Y Or BoundsSpecified.Height)
        PictureBox1.Bounds = New Rectangle(PictureBox1.Left, y, PictureBox1.Width, h)

        'リサイズされたので再描画を依頼
        PictureBox1.Invalidate()
    End Sub

    Private Sub NumericUpDown1_ValueChanged(sender As Object, e As EventArgs) 'Handles NumericUpDown1.ValueChanged
        ResizePicture()
    End Sub

    'DrawLine による描画処理。背景の塗り直しは行っていない。
    Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
        Dim r As Rectangle

        r = PictureBox1.ClientRectangle 'PictureBox 全体
        Using p As New Pen(Brushes.MediumTurquoise, 4)
            For y = r.Top To r.Bottom Step Math.Max(r.Height \ 20, 1)
                e.Graphics.DrawLine(p, r.Left, y, r.Right, y)
            Next
        End Using

        r = e.ClipRectangle '可視エリアのみ
        Using b As New SolidBrush(Color.FromArgb(218, Color.LightPink)), p As New Pen(b, 2)
            For y = r.Top To r.Bottom Step Math.Max(r.Height \ 20, 1)
                e.Graphics.DrawLine(p, r.Left, y, r.Right, y)
            Next
        End Using
    End Sub

#Region "ClipRectangle が変更されたら再描画"
    Private Sub PictureBox1_Resize(sender As Object, e As EventArgs) Handles PictureBox1.Resize
        PictureBox1.Invalidate()
    End Sub
    Private Sub Form1_ResizeEnd(sender As Object, e As EventArgs) Handles Me.ResizeEnd
        PictureBox1.Refresh()
    End Sub
    Private Sub Form1_Resize(sender As Object, e As EventArgs) Handles Me.Resize
        PictureBox1.Invalidate()
    End Sub
#End Region

End Class

引用返信 編集キー/
■88572 / inTopicNo.9)  Re[6]: コントロールを自然に動かすには??
□投稿者/ 熊さん (1回)-(2018/09/08(Sat) 16:01:04)
2018/09/08(Sat) 16:01:51 編集(投稿者)
ありがとうございます。
確かにこのコードだとほとんど残像が気になりませんね。

以下のコードが残像の出るコードです。

フォーム上に
PictureBox1とNumericUpDown1を配置して
ホイールでNumericUpDown1を回していただけますでしょうか?

すると位置が変化するはずのない赤色の中央線がぴくぴく動くことがご確認いただけると思います。

        PictureBox1.Invalidate()
を

        '   PictureBox1.Refresh()
すると少しましにはなるのですが
やはりぴくぴく動いてしまいます。


Public Class Form6


    Public YRate As Single

    Public ProfilePosition As New XY_Profile

    Public Class XY_Profile

        Property X_Int As Integer
        Property Y_Int As Integer

    End Class



    Private Sub Form6_Imaging_Load(sender As Object, e As EventArgs) Handles MyBase.Load


        PBox1 = PictureBox1.Bounds

        With NumericUpDown1
            .Minimum = 10
            .Maximum = 100
            .DecimalPlaces = 0
            .Increment = 3
            .Value = 100
        End With

        PictureBox1.SizeMode = PictureBoxSizeMode.Zoom


        ProfilePosition.X_Int = CInt((PictureBox1.Width - 3) / 2)
        ProfilePosition.Y_Int = CInt((PictureBox1.Height - 3) / 2)



    End Sub



    Private PBox1 As Rectangle


    Private pPen As New Pen(Color.FromArgb(60, Color.Red), 2)

    Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles PictureBox1.Paint

        Dim Pbox As PictureBox = CType(sender, PictureBox)

        Dim g As Graphics = e.Graphics
        g.DrawLine(pPen, 0, ProfilePosition.Y_Int, Pbox.Width, ProfilePosition.Y_Int)
        g.DrawLine(pPen, ProfilePosition.X_Int, 0, ProfilePosition.X_Int, Pbox.Height)

    End Sub



    Private Sub NumericUpDown1_ValueChanged(sender As Object, e As EventArgs) Handles NumericUpDown1.ValueChanged


        YRate = NumericUpDown1.Value / 100

        PictureBox1.SetBounds(PBox1.X, PBox1.Top + CInt(PBox1.Height * (1 - YRate) * 0.5), PBox1.Width, CInt(PBox1.Height * YRate))

        ProfilePosition.Y_Int = CInt(0.5 * (PictureBox1.Height - 4))

        '   PictureBox1.Refresh()

        PictureBox1.Invalidate()


    End Sub

End Class

引用返信 編集キー/
■88584 / inTopicNo.10)  Re[7]: コントロールを自然に動かすには??
□投稿者/ 魔界の仮面弁士 (1831回)-(2018/09/09(Sun) 01:09:10)
2018/09/09(Sun) 04:49:21 編集(投稿者)
No88572 (熊さん  さん) に返信
> すると位置が変化するはずのない赤色の中央線がぴくぴく動くことがご確認いただけると思います。

「位置がまったく変化していない」ことは確認済みでしょうか。

たとえば、現在の処理方法で詰めていくのなら、
  PictureBox1.SetBounds(…)
  ProfilePosition.Y_Int = …
の直後に
  Me.Text = CStr(PictureBox1.Top + ProfilePosition.Y_Int)
などを置いてみて、NumericUpDown を動かしたときに
タイトルバーに記載される値がブレないことを確認するなど。



> PBox1.Top + CInt(PBox1.Height * (1 - YRate) * 0.5)
上記の計算式の場合、YRate が Single なので、
0.5 や 0.5R ではなく、0.5F と書いた方が良いですよ。


さて、以下は提示頂いたコードとは別のやり方になるのですが:

中央線は、PictureBox の位置に合わせて調整するとはいえ、
Form 上から見れば、常に同じ位置にあるわけですよね。

であれば Load 時にでも、その中央線の交点座標を、
PictureBox1 の中での位置として保持するのではなく、
「Form のクライアント座標」に変換して保持されては如何でしょう。

そうすれば、NumericUpDown は PictureBox1 の位置調整に
特化して処理できますので、描画処理も簡潔になりそうな気がします。


ということで、書き直してみました。


Public Class Form6
    Private pPen As Pen         '十字線の描画用ペン
    Private PBox1 As Rectangle  '100% の時の PictureBox1 の位置情報
    Private CenterPos As Point  '十字線の中央位置(Formのクライアント領域基準)
    Private Sub Form6_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed
        pPen.Dispose()          '不要になった Pen の処分
    End Sub
    Private Sub Form6_Imaging_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        pPen = New Pen(Color.FromArgb(60, Color.Red), 2)    '十字線描画用
        PBox1 = PictureBox1.Bounds  '初期位置の保存

        '十字線の中央位置(PictureBoxのクライアント領域基準)
        Dim centerPic As New Point(PictureBox1.Width \ 2, PictureBox1.Height \ 2)

        '十字線の中央位置(Formのクライアント領域基準)
        CenterPos = centerPic + New Size(PictureBox1.Location)
    End Sub
    Private Sub PictureBox1_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles PictureBox1.Paint
        Dim Pbox As PictureBox = CType(sender, PictureBox)

        '十字線の中央位置(PictureBoxのクライアント領域基準)
        Dim xy = CenterPos - New Size(PictureBox1.Location)

        Dim g = e.Graphics
        g.DrawLine(pPen, 0, xy.Y, PictureBox1.Width, xy.Y)
        g.DrawLine(pPen, xy.X, 0, xy.X, PictureBox1.Height)
    End Sub

    Private Sub NumericUpDown1_ValueChanged(sender As Object, e As EventArgs) Handles NumericUpDown1.ValueChanged
        If PBox1.IsEmpty Then
            Return  'ロード前なので何もしない
        End If

        '位置調整の「前」に、描画状態を無効化しておく
        PictureBox1.Invalidate()

        '位置調整
        Dim val As Integer = CInt(NumericUpDown1.Value) '10〜100
        Dim sz As New Size(PBox1.Width, PBox1.Height * val \ 100)
        Dim xy As New Point(PBox1.Left, PBox1.Top + (PBox1.Height - sz.Height) \ 2)
        PictureBox1.Bounds = New Rectangle(xy, sz)
    End Sub
End Class


なお元のコードでは、座標の計算時に、
Decimal, Integer, Single, Double の型が混ざっていたので、
勝手ながらすべて Integer のみで演算されるよう改修しています。

ただし「CInt(z * 0.5)」を「z \ 2」で代用した関係上、
NumericUpDown の値によっては、PictureBox1 の位置と大きさが
ごく僅かにズレが生じることがあります。

具体的には、「CInt(z * 0.5)」と「z \ 2」の計算を比較すると、
z = 105 の場合は、どちらの計算でも 52 と算出されますが、
z = 103 の場合は、前者が 52、後者は 51 となるというものです。

引用返信 編集キー/
■88588 / inTopicNo.11)  Re[8]: コントロールを自然に動かすには??
□投稿者/ 熊さん (4回)-(2018/09/09(Sun) 13:25:43)
ありがとうございます。
問題解決しました。


ただ、少し以下気になった点です。


> > PBox1.Top + CInt(PBox1.Height * (1 - YRate) * 0.5)
> 上記の計算式の場合、YRate が Single なので、
> 0.5 や 0.5R ではなく、0.5F と書いた方が良いですよ。

これはなぜでしょうか?
0.5Fとしなかった場合、どういう問題があり得ますか?
確か、C++だと、エラーになるのでしたでしょうか?



PictureBox1.Invalidate()

PictureBox1.Bounds = New Rectangle(xy, sz)

の前にありますが、

PictureBox1.Invalidate()はPictureBox1に描画するように指令を出すためのコマンドのはずです。
それなら、
PictureBox1.Bounds = New Rectangle(xy, sz)
の後でないと設定したサイズが描画されないのではないでしょうか?
そもそも
'位置調整の「前」に、描画状態を無効化しておく
と、無効化しておく、と書いてあるのはなぜでしょうか?
本来の機能と正反対ではありませんか?

引用返信 編集キー/
■88589 / inTopicNo.12)  Re[9]: コントロールを自然に動かすには??
□投稿者/ 熊さん (5回)-(2018/09/09(Sun) 13:28:53)

後、もう一つ疑問です。

Private Sub Form6_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed
pPen.Dispose() '不要になった Pen の処分
End Sub

フォームを閉じる時に、pPenを解放する処理が書かれてありますが、
解放処理が必要な変数と不要な変数の違いは何でしょうか?


引用返信 編集キー/
■88592 / inTopicNo.13)  Re[9]: コントロールを自然に動かすには??
□投稿者/ 魔界の仮面弁士 (1832回)-(2018/09/09(Sun) 16:03:28)
No88588 (熊さん さん) に返信
> 問題解決しました。
なお No88572 で投稿いただいたコードだと、
>>   Me.Text = CStr(PictureBox1.Top + ProfilePosition.Y_Int)
の実験で、中央線の座標に ±1 程度のブレが見られましたので、
そもそもの計算で誤差が生じていた、というのも原因の一つかと思います。
(それは残像ではなく、計算ミスということになりそうですが)


> PictureBox1.Invalidate()はPictureBox1に描画するように指令を出すためのコマンドのはずです。
ちょっと違います。


【Invalidate】の仕事は、そのウィンドウ内に『更新リージョン』を作成するだけであり、
正確には描画依頼そのものではありません。

ざっくり言えば、「現在の描画内容が無効である」とマーキングするためのもの。
今回は引数無しで呼び出しているので、領域すべてが無効化されます。
https://msdn.microsoft.com/ja-jp/library/cc410421.aspx


【Update】は、その更新リージョンが空ではない場合に、そのコントロールに対して
WM_PAINT ウィンドウメッセージを送信してクライアント領域を更新します。
これにより Paint イベントが誘発されます。
(更新すべき領域が何もなければ、呼び出しても何も起きません)

ウィンドウメッセージは通常、待機行列にキューイングされた上で、
アイドル時に順次処理されるようになっているのですが、Update メソッドを
呼び出した場合は、その待機行列を通さずに直接再描画メッセージが飛んできます。
https://msdn.microsoft.com/ja-jp/library/cc428780.aspx


【Refresh】は、Invalidate + Update と同義です。
直ちに再描画を行い時にのみ呼び出します。


PictureBox の上に他のウインドウが覆いかぶさっていて、それが取り除かれた時に
再描画されるのも、その重なっていた領域分の更新リージョンが生成されるためです。
この場合は明示的に Update を呼ばずとも、アイドル時に自動的に Paint イベントが呼ばれます。


>> '位置調整の「前」に、描画状態を無効化しておく
>> PictureBox1.Invalidate()
> が
> PictureBox1.Bounds = New Rectangle(xy, sz)
> の前にありますが、
Update や Refresh ではなく、Invalidate である点がミソです。


> PictureBox1.Bounds = New Rectangle(xy, sz)
> の後でないと設定したサイズが描画されないのではないでしょうか?
Bounds の直後に呼ぶとしたら、Invalidate ではなく Update または Refresh ですよ。

今回は Update を呼び出していませんが、それはリサイズ直後に End Sub しているからです。
アイドル時に Invalidate された領域への再描画が発生するので、
あえて Update を呼ぶ必要も無いという訳ですね。


それともう一つ。現在の描画状態を無効化せずに Top 位置を変えると、
以前描いた中央線ごと移動してしまうので、一瞬、正しくない位置に線があるように
見えてしまうという問題もあります。だからこそ、「直前」の無効化が重要となります。

直前には Invalidate を呼ばず、直後に呼び出してみるとか、
直前の Invalidate を Refresh に変えて呼び出してみると、
これらの違いを体感できるかと思います。
引用返信 編集キー/
■88593 / inTopicNo.14)  Re[10]: コントロールを自然に動かすには??
□投稿者/ 魔界の仮面弁士 (1833回)-(2018/09/09(Sun) 16:46:58)
No88589 (熊さん さん) に返信
> Private Sub Form6_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed
>   pPen.Dispose() '不要になった Pen の処分
> End Sub
> フォームを閉じる時に、pPenを解放する処理が書かれてありますが、
> 解放処理が必要な変数と不要な変数の違いは何でしょうか?

一口に答えるのは難しいのですが、ざっくり言うと、
「アンマネージリソースを保持しているかどうか」でしょうか。

こういったオブジェクトは多くの場合、IDisposable インターフェイスを通じて
Dispose メソッドを有していますので、それを呼び出すようにします。

たとえば、ファイルやデータベース接続などが分かりやすいかと思います。
ファイルを開いたまま閉じなければ、他のアプリケーションがそれを開けません。
データベース接続を開く一方で閉じずにいれば、接続資源が枯渇します。


あるいは、Control や Form 等のオブジェクトも IDisposable です。

たとえば ShowDialog メソッドの呼び出しにおいては、
 Using dlg As New Form2()
  dlg.ShowDialog(Me)
 End Using
とすべきであり、最後に Dispose することなく
 Dim dlg As New Form2()
 dlg.ShowDialog(Me)
とだけ書くのは NG です。
ただし Show メソッドの場合は Dispose が自動的に呼ばれますので、明示的な Dispose は不要です。


Control を動的に追加/削除する場合も同様で、
意図的に .Controls.Remove あるいは .Clear するような場合には、
取り除かれたコントロールを、明示的に Dispose しなければなりません。
Remove せず、Form の Controls に登録したままであれば、Form が Dispose される時に
子孫が連鎖的に Dispose されるので、明示的に Dispose する必要は無いですけれど。


そして、Pen、Brush、Font、Grapchis、Bitmap などもまた、Dispose 対象です。

ただし、Pens.Black や Brushes.Red などといったものに関しては、
自身で作成したリソースでは無く、Shared メンバーで管理される共有資源なので
この場合は Dispose してはいけません。(Dispose 後に再作成する手段がない)

また、Grapchis についても、Dispose が必要となるのは、
.CreateGraphics メソッドや Graphics.FromImage メソッドなどのように
自身で作成したリソースだけです。Paint イベントの e.Graphics などは
自身で作成したものではないので、勝手に Dispose してはいけません。


……ということで、Dispose メソッドがあれば常に呼び出すというわけでも
無いという点が、こういった判断を難しくしています。
StreamReader / StreamWriter なども、意図的に Dispose を呼ばないケースがありますし、
DataSet / DataTable などのように、Dispose しても何も起きないものなどもあります。



No88588 (熊さん さん) に返信
>>> PBox1.Top + CInt(PBox1.Height * (1 - YRate) * 0.5)
>>上記の計算式の場合、YRate が Single なので、
>>0.5 や 0.5R ではなく、0.5F と書いた方が良いですよ。
> これはなぜでしょうか?

一言で言えば、「データ型を意識しましょう」ということです。
Single 精度で演算したいのか、Double 精度で演算したいのか。

0.5 や 0.5R や 0.5# というのは Double 型のリテラルであり、
0.5F や 0.5! と書いた場合には Single 型のリテラルを意味します。
0.5D や 0.5@ と書いた場合には Decimal 型のリテラルを意味します。


先ほどの
 PBox1.Top + CInt(PBox1.Height * (1 - YRate) * 0.5)
の型を見てみると、
 Integer + CInt( Integer * ( Integer - Single ) * Double )
となっていますので、演算を進めていくと
 Integer + CInt( Integer * ( Single ) * Double )
 Integer + CInt( Single * Double )
 Integer + CInt( Double )
 Integer + Integer
の精度で計算されることになりますね。



>> YRate = NumericUpDown1.Value / 100
この代入式も、左辺の型が Single ですので、現状は
 Single 変数 = Decimal 値 / Integer 値
という計算式になっています。

「Decimal / Integer」は Decimal 型の精度で演算される仕様であり、
Single 変数 に Decimal 値を代入しようとしていることに注意してください。

Single は Decimal よりも表せる範囲が広いため、
この暗黙変換は Option Strict On でも成功します。
しかし有効桁数の精度的には Single の方が荒く、その分誤差を生じます。
あとは、この誤差をどう考えるかです。

※Decimal は 128bit のサイズを持つ型ですが、Single は 32bit しかありません。
※Decimal の有効桁数は約 28.899 桁、Single の有効桁数は約 7.2247 桁です。


後々 Double 精度で演算したいなら、YRate も Double の方が手っ取り早いですし
Single 精度で十分なら、先の座標計算も Single 型の計算式を組むべきでしょう。
あるいは誤差を減らしたいなら、パフォーマンスさえ許容範囲なら Decimal が使えますし、
あるいはパフォーマンス優先で、先のように Integer な演算式を組むこともできるわけです。

Integer 単位の Point/Rectangle 型や
Single 単位の PointF/RectangleF 型はあっても
Double 単位の PointR/RectangleR 型が無いというのも
型を選ぶ指針になるのではないでしょうか。


Single や Double は、2 進小数型ですので、0.50 や 0.25 は正しく保持できても、
0.01 という値は有限桁で表せず、わずかに誤差を含んでしまいます。
引用返信 編集キー/
■88595 / inTopicNo.15)  Re[10]: コントロールを自然に動かすには??
□投稿者/ 熊さん (6回)-(2018/09/09(Sun) 19:47:15)


ありがとうございます。

ただ、【Invalidate】のことがやはりよく理解できませんでした。

Invalidateとは更新予約するもので
Refleshは即時に更新するものであることは分かります。

例えば、上でご呈示くださったコードでは

PictureBox1.Bounds = New Rectangle(PictureBox1.Left, y, PictureBox1.Width, h)

'リサイズされたので再描画を依頼
PictureBox1.Invalidate()

リサイズの後に使用しているのはなぜでしょうか?

また、他のサンプルプログラムを見ていても、後に出てくるのがほとんどだと思います。
http://wisdom.sakura.ne.jp/system/msnet/msnet_win21.html
https://devlights.hatenablog.com/entry/20080509/p1


Boundsの後にもInvalidateを入れてみたりして、
うまく描画されず、Boundsの前である必要があることは分かるのですが
なぜ「今回だけ」前に入れるのかが分かりません。

前に入れる場合と後ろに入れる場合の使い分けについて教えていただけないでしょうか?


例えば、画像を表示したい場合、

With PBox

.Image = img
.Invalidate()

End With

Invalidateを
.Image = img

の前に入れても後に入れても、同じように動作しますが
どちらに入れるのが正しいのでしょうか?







引用返信 編集キー/
■88596 / inTopicNo.16)  Re[11]: コントロールを自然に動かすには??
□投稿者/ 魔界の仮面弁士 (1835回)-(2018/09/09(Sun) 20:22:17)
No88595 (熊さん さん) に返信
> リサイズの後に使用しているのはなぜでしょうか?
特に意図は無いですが、強いて言えば、前後どちらであっても、
気になるような差が出なかったら、という程度ですね。

先のように、移動前後で座標がまったくずれないようにしたい場合には
気にかけた方が良いと思います。


> http://wisdom.sakura.ne.jp/system/msnet/msnet_win21.html
これは、クライアント領域の再描画が行われているだけであって、
ウィンドウ位置が変化しているわけではないのですから、
今回の話とは関係無いですよね。



> 例えば、画像を表示したい場合、
> の前に入れても後に入れても、同じように動作しますが
> どちらに入れるのが正しいのでしょうか?
どちらも不要かと思いますよ。
PitureBox.Image は、新しい画像がセットされたときに
内部で自動的に Invalidate を呼び出していますから。
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ