2014年9月21日日曜日

縦書きテキストボックスコントロールの作成(その12) スクロールバーの設定



今回はスクロールバーの設定に着手したいと思います。
プロパティ名
ScrollBars
複数行エディットコントロールで、このコントロールに対してどのスクロールバーを表示するかを示します

スクロールバーの設定は画像のような感じで4種類。

 

プロパティ名
定数
None
0
スクロールは表示しません。
Horizontal
1
水平スクロールだけ表示されます。
Vertical
2
垂直スクロールだけ表示されます。
Both
3
水平及び垂直するロールバーが表示されます。

調べてみると、「ShowScrollBar」関数を使用すれば簡単そうです。

''' <summary>
''' 指定したスクロールバーを表示または非表示にする。
''' </summary>
''' <param name="hWnd">ウインドウのハンドル</param>
''' <param name="wBar">スクロールバーの種類</param>
''' <param name="bShow">表示、非表示</param>
''' <returns></returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Friend Function ShowScrollBar(ByVal hWnd As IntPtr, ByVal wBar As fnBars, ByVal bShow As Boolean) As IntPtr
End Function
''' <summary>
''' スクロールバーのタイプ
''' </summary>
''' <remarks></remarks>
Friend Enum fnBars As Integer
    ''' <summary>水平を指定</summary>
    SB_HORZ = 0
    ''' <summary>垂直の指定</summary>
    SB_VERT = 1
    ''' <summary>スクロールを指定</summary>
    SB_CTL = 2
    ''' <summary>水平・垂直</summary>
    SB_BOTH = 3
End Enum
''' <summary>
''' 現在のスクロール設定値
''' </summary>
''' <remarks>コントロール作成時は「WS_HSCROLL + WS_VSCROLL」と両指定している場合は「4」とする。</remarks>
Private _ScrollBars As ScrollBars = 0

''' <summary>
''' 縦書テキストボックスのスクロールバーの設定
''' </summary>
''' <value>設定するスクロール値</value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ScrollBars As ScrollBars
    Get
        Return _ScrollBars
    End Get
    Set(value As ScrollBars)
        '設定値が同じ場合は何もしない。
        If _ScrollBars = value Then Exit Property
        Call ShowScrollBar(_Handles.TextBox, fnBars.SB_BOTH, False)
        Call ShowScrollBar(_Handles.TextBox, fnBars.SB_VERT, False)
        Call ShowScrollBar(_Handles.TextBox, fnBars.SB_HORZ, False)

        Select Case value
            Case None
                Call ShowScrollBar(_Handles.TextBox, value, False)

            Case Else
                Call ShowScrollBar(_Handles.TextBox, fnBars.SB_CTL, True)
                Select Case value
                    Case Horizontal
                        Call ShowScrollBar(_Handles.TextBox, fnBars.SB_HORZ, True)
                    Case Vertical
                        Call ShowScrollBar(_Handles.TextBox, fnBars.SB_VERT, True)
                    Case Both
                        Call ShowScrollBar(_Handles.TextBox, fnBars.SB_BOTH, True)
                End Select

        End Select
        _ScrollBars = value
    End Set
End Property

ScrollBars」プロパティの呼出しの件については、なぜ加工しないと切り替えた時にうまく機能しなかったためです。
おそらく、スクロールバーそれぞれが一つのウインドウとして機能しており、一つずつ指定してやらないといけないから、、、なのかなぁ?と。

 



さて、ここで注意点が。
CreateWindowEx」時に「WS_VSCROLL + WS_HSCROLL」を宣言せず、改めて(つまり、「ShowScrollBar」)で初めて表示などを行った場合、ステータスバーの動作について全部手動でやってやらないといけない状況が発生。
はっきり言ってめんどくさいので、非表示の場合も「CreateWindowEx」時で最初に宣言して「ShowScrollBar」で非表示などの切り替えを行うのが好ましいですね。
またこの場合、文字列がボックス表示サイズを超えると、自動的にスクロールバーが表示されるようになります。
いろいろ試しましたが、どうにも直らないので仕方がなく、スクロールバーの再描画されるタイミングでスクロールバーを再設定するように調整しました。
以下が変更後のコード。

''' <summary>
''' 縦書テキストボックスのスクロールバーの設定
''' </summary>
''' <value>設定するスクロール値</value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ScrollBars As ScrollBars
    Get
        Return _ScrollBars
    End Get
    Set(value As ScrollBars)
        If _Handles.TextBox = IntPtr.Zero Then Exit Property
        _ScrollBars = value
        Call ScrollBarsReSet()
    End Set
End Property
''' <summary>
''' スクロールバーを再設定する。
''' </summary>
''' <remarks></remarks>
Public Sub ScrollBarsReSet()
    Select Case ScrollBars
        Case None
            Call ShowScrollBar(_Handles.TextBox, fnBars.SB_VERT, False)
            Call ShowScrollBar(_Handles.TextBox, fnBars.SB_HORZ, False)
        Case Vertical
            Call ShowScrollBar(_Handles.TextBox, fnBars.SB_HORZ, False)
            Call ShowScrollBar(_Handles.TextBox, fnBars.SB_VERT, True)
        Case Horizontal
            Call ShowScrollBar(_Handles.TextBox, fnBars.SB_VERT, False)
            Call ShowScrollBar(_Handles.TextBox, fnBars.SB_HORZ, True)
        Case Both
            Call ShowScrollBar(_Handles.TextBox, fnBars.SB_HORZ, True)
            Call ShowScrollBar(_Handles.TextBox, fnBars.SB_VERT, True)
    End Select
End Sub
WndProc内のタイミングはスクロールバーが作成された場合、ウインドウのサイズ変更メッセージが送信されるようですので、そのタイミングで上記のスクロールバーの再設定処理プロシージャを読んでやることにしました。
    If hWnd = _Handles.TextBox Then
          If KeyCheck(uMsg, wParam) = False Then
              Select Case uMsg
                  Case WM_SIZE
                      Call ScrollBarsReSet()
              End Select
          End If
    End If
なお、「WM_PAINT」でもよいのでしょうが、それだと呼び出し回数が多く、負荷をかけてしまう関係で「WM_SIZE」にて暫定的に実施してやりました。

しかしこれでも、偶にちらつく感じでスクロールバーが一瞬表示されてしまうという状況ですので、それを取り除いてやるためには別のタイミング(「WM_PAINT」)でも実施する必要があるかもしれません。
何とか別の方法で毎回再設定しなくて済むように調整する必要があるんでしょうが、現状はこのままで次に進みます。

スクロールの表示が終わったら今度はスクロールの最大値と最小値の取得と設定及び現在値の取得と設定です。
個別に用意されているスクロールバーコントロールでは様々な設定ができますが、今回はコントロール付属のスクロールバーであり、最低限必要だろうと思うのがその三点だったのでそのように。
とはいえ、最小値は0で固定でよく、最大値は自動で変動するので、取得だけで設定までは不要だろうなと思います。
今回、一応は設定できるまで記載しますが、デバックの対象外とするので設定する場合は各自で動作の確認は行ってください。
スクロールバーの情報の取得と設定は主に「SetScrollInfo」関数と「GetScrollInfo」関数を使用します。
他にも「SetScrollPOS」など、直接指定するものもありますが、MSDSの方には
「下位互換性の目的で提供されています。新しいアプリケーションは代わりに「SetScrollInfo」関数を使うべきです」
と、使うことに対して警告を発していますので、使用しない方向で作成します。

まずはAPIの宣言から。
''' <summary>
''' スクロールバーに関する情報を設定します。
''' </summary>
''' <param name="hWnd">ウインドウのハンドル</param>
''' <param name="nBar">設定するスクロールの種類</param>
''' <param name="lpScrollInfo">スクロールに関する情報が格納されている構造体</param>
''' <param name="fRedraw">再描画する場合はTRUE、そのままの場合はFALSE</param>
''' <returns></returns>
''' <remarks></remarks>
Friend Declare Function SetScrollInfo Lib "user32.dll" (ByVal hWnd As IntPtr, _
                              <MarshalAs(UnmanagedType.I4)> ByVal nBar As fnBars, _
                              <MarshalAs(UnmanagedType.Struct)> ByRef lpScrollInfo As SCROLLINFO, _
                              <MarshalAs(UnmanagedType.Bool)> ByVal fRedraw As Boolean) As IntPtr
''' <summary>
''' スクロールバーに関する情報を取得します。
''' </summary>
''' <param name="hWnd">ウインドウのハンドル</param>
''' <param name="fnBar">対象となるスクロールバーを指定。</param>
''' <param name="lpsi">スクロールバーに関する構造体</param>
''' <returns></returns>
''' <remarks></remarks>
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
Friend Function GetScrollInfo(ByVal hWnd As IntPtr, ByVal fnBar As fnBars, ByRef lpsi As SCROLLINFO) As IntPtr
End Function
API関数で使用する構造体の宣言
''' <summary>
''' スクロールバーのステータス
''' </summary>
''' <remarks></remarks>
Public Structure SCROLLINFO
    ''' <summary>構造体のバイト数を指定</summary>
    Public cbSize As UInteger
    ''' <summary>パラーメータオプションフラグ</summary>
    Public fMask As UInteger
    ''' <summary>スクロールの最小値を指定</summary>
    Public nMin As Integer
    ''' <summary>スクロールの最大値を指定</summary>
    Public nMax As Integer
    ''' <summary>ページサイズを指定</summary>
    Public nPage As UInteger
    ''' <summary>スクロールの現在位置を指定</summary>
    Public nPos As Integer
    ''' <summary>現在のトラッキング位置が格納(GetScrollInfo関数のみ)</summary>
    Public nTrackPos As Integer
End Structure

Public Enum fMasks As UInteger
    ''' <summary>RANGE,PAGE,POS,TRACKPOSの各定数を組み合わせたフラグ</summary>
    SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS
    ''' <summary>スクロールバーが不要になったときは、非表示ではなく、無効状態にします(SetScrollInfo関数のみ)</summary>
    SIF_DISABLENOSCROLL = &H8
    ''' <summary>nPageメンバを有効にします。</summary>
    SIF_PAGE = &H2
    ''' <summary>nPosメンバを有効にします。</summary>
    SIF_POS = &H4
    ''' <summary>nMinメンバ及びnMaxメンバを有効にします</summary>
    SIF_RANGE = &H1
    ''' <summary>nTrackPOSメンバを有効にします(GetScrollInfo関数のみ)</summary>
    SIF_TRACKPOS = &H10
End Enum


''' <summary>
''' スクロールバーのタイプ
''' </summary>
''' <remarks></remarks>
Public Enum fnBars As Integer
    ''' <summary>水平を指定</summary>
    SB_HORZ = &H0
    ''' <summary>垂直の指定</summary>
    SB_VERT = &H1
    ''' <summary>スクロールを指定</summary>
    SB_CTL = &H2
    ''' <summary>水平・垂直</summary>
    SB_BOTH = &H3
End Enum

で、後々使用する定数。(今回は使用するのものだけ宣言)
''' <summary>スクロールボックスを操作した</summary>
Public Const SB_THUMBPOSITOPN As Integer = 4

GetScrollInfo関数では、水平スクロールと垂直スクロールの両方を一度に取得することができませんので、それぞれ取得するためのプロシージャを宣言します。
''' <summary>
''' 水平スクロールバーのステータス情報を取得します。
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property ScrollBarInfoHORZ As SCROLLINFO
    Get
        If _Handles.TextBox = IntPtr.Zero Then Exit Property
        Hscr.fMask = fMasks.SIF_ALL
        Hscr.cbSize = SizeOf(Hscr)
        Call GetScrollInfo(_Handles.TextBox, fnBars.SB_HORZ, Hscr)
        Return Hscr
    End Get
End Property


''' <summary>
''' 垂直スクロールバーのステータス情報を取得します。
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public ReadOnly Property ScrollBarInfoVERT As SCROLLINFO
    Get
        If _Handles.TextBox = IntPtr.Zero Then Exit Property
        Vscr.fMask = fMasks.SIF_ALL
        Vscr.cbSize = SizeOf(Vscr)
        Call GetScrollInfo(_Handles.TextBox, fnBars.SB_VERT, Vscr)
        Return Vscr
    End Get
End Property
なお、「SizeOf」関数を使用するために、事前に「Imports System.Runtime.InteropServices.Marshal」と宣言しておくのを忘れないようにしておきます。

次に最大値と最小値に特化したプロシを準備します。
''' <summary>
''' 水平スクロールバーの最大値の設定と取得を行います。
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ScrollBarMax_HORZ As Integer
    Get
        Hscr.fMask = fMasks.SIF_ALL
        Hscr.cbSize = SizeOf(Hscr)
        Call GetScrollInfo(_Handles.TextBox, fnBars.SB_HORZ, Hscr)
        Return Hscr.nMax - Hscr.nPage
    End Get
    Set(value As Integer)
        If _Handles.TextBox = IntPtr.Zero Then Exit Property
        Hscr.fMask = fMasks.SIF_RANGE
        Hscr.cbSize = SizeOf(Hscr)
        Hscr.nMax = value
        Call SetScrollInfo(_Handles.TextBox, fnBars.SB_HORZ, Hscr, True)
    End Set
End Property
上のプロシは水平の最大値となりますが、垂直を取得したい場合は「fnBars.SB_VERT」に変えてやるだけです。(変数もHscrからVscrにします)
最小値を取得、設定したい場合は「.nMax」を「.nMin」に変えてやるだけでOKです。

次に現在値の取得となります。
''' <summary>
''' 水平スクロールバーの現在位置の設定と取得を行います。
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ScrollBarValue_HORZ As Integer
    Get
        Hscr.fMask = fMasks.SIF_POS
        Hscr.cbSize = SizeOf(Vscr)
        Call GetScrollInfo(_Handles.TextBox, fnBars.SB_HORZ, Hscr)
        Return Hscr.nTrackPos
    End Get
    Set(value As Integer)
        If _Handles.TextBox = IntPtr.Zero Then Exit Property
        If Vscr.nPos = value Then Exit Property
        Static HChange As Boolean
        If HChange = False Then
            HChange = True
            With Hscr
                .cbSize = SizeOf(Hscr)
                .fMask = fMasks.SIF_POS
                .nPos = value
            End With
            Call SetScrollInfo(_Handles.TextBox, fnBars.SB_HORZ, Hscr, True)
            Call SendMessage(_Handles.TextBox, WM_HSCROLL, MakeParam(SB_THUMBPOSITOPN, value), 0)
            HChange = False
        End If
    End Set
End Property
上のコードは水平のみです。垂直を指定したい場合は先ほどと同じ感じで修正してください。
なお、「Call SendMessage(_Handles.TextBox, WM_HSCROLL, MakeParam(SB_THUMBPOSITOPN, value), 0)」について補足します。
これを指定しない場合、スクロールの位置は変更しますが、ウインドウの位置は移動しない状況になります。なので、スクロールとテキストボックスを同期してやる必要があり、そのための一文となります。垂直の場合は「WM_HSCROLL」を「WM_VSCROLL」に変えてやってください。
もう一つ補足を。 この時の「wParam」の値は、上位ワードに現在値を指定してやり、下位ワードにスクロールのメッセージ定数を格納してやる必要があります。
MakeParam関数はそのために作成した格納用関数となります。

Friend Function MakeLong(ByVal wLow As Long, wHight As Long) As Long
    Return LOWORD(wLow) Or (&H10000 * LOWORD(wHight))
End Function

Friend Function MakeParam(ByVal wLow As Long, wHight As Long) As Long
    Return MakeLong(wLow, wHight)
End Function
下記画面は、「GetStatus」のボタンで現在のステータス情報を「TrackScroll」コントロールに格納し、TrackScrollのつまみにテキストボックスのスクロールバーが連動するように調整したものです。
つまみの変動に合わせてスクロールバーの現在位置も変更し、ボックスの位置も変動しているのが確認できます。


最後にスクロールした時のイベントを作成します。

まず、ユーザーコントロール内に下記のイベント告知用コードを作成します。
''' <summary>
''' スクロールバーイベント
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Public Shadows Event ScrollChange(sender As Object, e As System.Windows.Forms.ScrollEventArgs)
''' <summary>
''' スクロールイベント発生用プロシ
''' </summary>
''' <param name="e"></param>
''' <remarks></remarks>
Friend Shadows Sub ScrollEvent(e As System.Windows.Forms.ScrollEventArgs)
    RaiseEvent ScrollChange(Me, e)
End Sub

次に、WnProc内で「WM_VSCROLL」「WM_HSCROLL」が飛んできたときに処理を行うための関数を作成します。
''' <summary>
''' スクロールバー処理
''' </summary>
''' <param name="m"></param>
''' <remarks>スクロールイベントの場合はTrueを、そうでない場合はFalseを返す</remarks>
Friend Function ScrollBarMessage(ByRef m As Message) As Boolean
    Select Case m.Msg
        Case WM_VSCROLL, WM_HSCROLL
            Dim eType As System.Windows.Forms.ScrollEventType = LOWORD(m.WParam)
            Dim Pos As Integer = HIWORD(m.WParam)
            Dim oldValue As Integer = 0
            Dim SO As ScrollOrientation
            If m.Msg = WM_VSCROLL Then
                SO = ScrollOrientation.VerticalScroll
                oldValue = Vscr.nPos
                Vscr.nPos = Pos
            Else
                SO = ScrollOrientation.HorizontalScroll
                oldValue = Hscr.nPos
                Hscr.nPos = Pos
            End If
            Dim e As New System.Windows.Forms.ScrollEventArgs(eType, oldValue, Pos, SO)
            Call MeControl.ScrollEvent(e)
        Case Else
            Return False
    End Select
    Return True
End Function
あとは上記コードをWndProc内にぶち込んでやれば終了。

0 件のコメント:

コメントを投稿