DOBON.NET DOBON.NETプログラミング掲示板過去ログ

ListViewのハイライトの色設定

環境/言語:[xp / VB.NET ]
分類:[.NET]

お世話になります。
タイトルどおりです。

ListViewの選択され、ハイライトになっている行の
背景色と文字色を変更したいです。
(複数行の選択はNGとしています。)
DataGridViewでいう
DefaultCellStyle.SelectionBackColor

DefaultCellStyle.SelectionForeColor
のようなものをイメージしています。
どのような方法があるのか教えていただきたいのです。

さらに上記を
SelectedIndexChanged(?)イベントで実行したいのですが
TabControlに配置した20個のListViewが対象になります。
やはり20個のイベントを書かなければならないのでしょうか?
一括してコーディングできる方法があれば
あわせて教えてください。

よろしくお願いします。
> ListViewの選択され、ハイライトになっている行の
> 背景色と文字色を変更したいです。
> (複数行の選択はNGとしています。)
> DataGridViewでいう
> DefaultCellStyle.SelectionBackColor
> や
> DefaultCellStyle.SelectionForeColor
> のようなものをイメージしています。
> どのような方法があるのか教えていただきたいのです。

これは OS 依存で無理っぽいです。
少なくとも ListView にフォーカスがあるうちは無理。
ListView からフォーカスが外れれば、選択行の色変更は可能です。

以下サンプルです。
ListView は View を Details にし、FullRowSelect を True、
MultiSelect を False に設定してます。

で、ボタンクリックイベントなら色は変わるが、SelectedIndexChanged イベント等では駄目。

    Private Sub Button1_Click
         (ByVal sender As System.Object, ByVal e As System.EventArgs)  
     Handles Button1.Click
        For Each item As ListViewItem In ListView1.Items
            If (item.Selected) Then
                item.BackColor = Color.PeachPuff
                item.ForeColor = Color.Red
            Else
                item.BackColor = SystemColors.Window
                item.ForeColor = SystemColors.WindowText
            End If
        Next
    End Sub
ひらぽんさん、ありがとうございます。
レス、遅くなりまして申し訳ありません。

> これは OS 依存で無理っぽいです。
> 少なくとも ListView にフォーカスがあるうちは無理。
> ListView からフォーカスが外れれば、選択行の色変更は可能です。

OS依存ですか、残念です。
何とかして色変更したかったのですが。
TreeViewも同じくOS依存なのでしょうか?

もう少しお付き合いください。
> 何とかして色変更したかったのですが。
> TreeViewも同じくOS依存なのでしょうか?

残念ながらそのようです。
オーナードローにより全て自前で描画する、という手段は存在します。かなりめんどくさいですが。
// View が Details の場合、フォーカス四角形の Rectangle を手に入れる方法が自前の計算によるしかないっぽいのもネック。
2010/01/18(Mon) 23:02:55 編集(投稿者)
2010/01/18(Mon) 23:01:06 編集(投稿者)

お疲れ様です。
 
後だしジャンケンのようで申し訳ないですが(その意思はありません)
jacoさんの参考になればと思い、簡単なサンプルを投稿させていただきました。
(DrawFocusRectangleは手抜きしています)
 
参考URL
ttp://msdn.microsoft.com/ja-jp/library/system.windows.forms.listview.drawsubitem(VS.80).aspx
 
' ListView1を配置してください
Public Class Form_ListViewTest
 
    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
        With Me.ListView1
            .View = View.Details
            .OwnerDraw = True
            .Columns.Add("列1")
            .Columns.Add("列2")
            For i As Integer = 0 To 10
                .Items.Add(New ListViewItem( _
                    New String() {"値1−" + i.ToString, "値2−" + i.ToString}))
            Next
        End With
 
        MyBase.OnLoad(e)
    End Sub
 
    Private Sub ListView1_DrawColumnHeader(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.DrawListViewColumnHeaderEventArgs) Handles ListView1.DrawColumnHeader
        e.DrawDefault = True
    End Sub
 
    Private Sub ListView1_DrawItem(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.DrawListViewItemEventArgs) Handles ListView1.DrawItem
        If e.Item.Selected Then
            e.Item.BackColor = Color.Orange
        Else
            e.Item.BackColor = SystemColors.Window
        End If
 
        If Me.ListView1.View <> View.Details Then
            e.DrawBackground()
            e.DrawText()
        End If
    End Sub
 
    Private Sub ListView1_DrawSubItem(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.DrawListViewSubItemEventArgs) Handles ListView1.DrawSubItem
        If e.Item.Selected Then
            e.SubItem.ForeColor = Color.Red
        Else
            e.SubItem.ForeColor = SystemColors.WindowText
        End If
        e.DrawBackground()
        e.DrawText()
    End Sub
End Class
H.K.R.さん、ありがとうございます。

サンプル、早速検証させていただきました。
意図する動作を確認できました。
大変参考になりましたし、勉強させていただきました。

ただ、
GridLines = True
に設定すると
最初にマウスのポインタを
ListView上にもっていくと
グリッド線が消えてしまう、
といった現象が起きました。
この現象を回避する方法がありましたら
引き続き教えていただきたいのです。

よろしくお願いします。
お疲れ様です。

GridLines = Trueに対応するため、
ListView1_DrawSubItemイベントハンドラを修正しました。
私のPCではうまく行きましたので、試してみてください。

    Private Sub ListView1_DrawSubItem(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.DrawListViewSubItemEventArgs) Handles ListView1.DrawSubItem
        If e.Item.Selected Then
            e.SubItem.ForeColor = Color.Red
        Else
            e.SubItem.ForeColor = SystemColors.WindowText
        End If
        e.DrawBackground()
        e.DrawText()

        If Me.ListView1.GridLines Then
            Dim rect As Rectangle = e.Bounds
            ' この辺は苦し紛れ(座標の微調整)
            rect.Offset(0, -SystemInformation.BorderSize.Height)
            ' 上・右・下の三方に線を描画
            Dim points As Point() = {New Point(rect.Left, rect.Top), New Point(rect.Right, rect.Top), _
                New Point(rect.Right, rect.Bottom), New Point(rect.Left, rect.Bottom)}
            e.Graphics.DrawLines(SystemPens.ControlLight, points)
        End If
    End Sub
H.K.R.さん、ありがとうございます。

> 私のPCではうまく行きましたので、試してみてください。
こちらの環境でも確認できました。
凄い、の一言です。

調子に乗ってTreeViewでも同じく
強調表示の背景・文字色を設定したいということで
自分なりに応用してできないものかと
TreeViewのDrawNodeを使って
試してはいるのですが
どうにも動いてくれません。

TreeViewとListViewはまったく考え方が違うのでしょうか?
もう少しだけお付き合いください。
よろしくお願いします。
お疲れ様です。

以下のコードで試してみてください。
(参考URL)
ttp://msdn.microsoft.com/ja-jp/library/system.windows.forms.treeview.drawnode(VS.80).aspx

Public Class Form_TreeViewTest
    Inherits Form

    Dim WithEvents TreeView1 As New TreeView

    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
        With Me.TreeView1
            .Dock = DockStyle.Fill
            .DrawMode = TreeViewDrawMode.OwnerDrawText
            With .Nodes.Add("ノード1")
                .Nodes.Add("子ノード1")
                .Nodes.Add("子ノード2")
                .Nodes.Add("子ノード3")
            End With
        End With

        Me.Controls.Add(Me.TreeView1)

        MyBase.OnLoad(e)
    End Sub

    Private Sub TreeView1_DrawNode(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.DrawTreeNodeEventArgs) Handles TreeView1.DrawNode

        Dim textColor As Color = SystemColors.WindowText
        Dim backgroundColor As Color = SystemColors.Window

        If e.Node.IsSelected Then
            textColor = Color.Green
            backgroundColor = Color.Orange
        End If

        Using backBrush As New SolidBrush(backgroundColor)
            e.Graphics.FillRectangle(backBrush, e.Node.Bounds)
        End Using

        Using textBrush As New SolidBrush(textColor)
            Dim nodeFont As Font = e.Node.NodeFont
            If nodeFont Is Nothing Then
                nodeFont = Me.TreeView1.Font
            End If
            e.Graphics.DrawString(e.Node.Text, nodeFont, textBrush, _
                                  e.Node.Bounds.Left, e.Node.Bounds.Top + 1)
        End Using

        ' Focus枠を描画(少し苦しいコード)
        If e.Node.IsSelected Then
            Using pen As New Pen(Color.Black)
                Dim rect As Rectangle = e.Node.Bounds
                Dim points As Point() = {New Point(rect.Left, rect.Top), _
                                         New Point(rect.Right - 1, rect.Top), _
                                         New Point(rect.Right - 1, rect.Bottom - 1), _
                                         New Point(rect.Left, rect.Bottom - 1), _
                                         New Point(rect.Left, rect.Top)}
                e.Graphics.DrawLines(pen, points)
                pen.Color = SystemColors.ControlLight
                pen.DashStyle = Drawing2D.DashStyle.Dot
                e.Graphics.DrawLines(pen, points)
            End Using
        End If

    End Sub

End Class
H.K.R.さん、ありがとうございます。

またまたイメージどおりの動作を確認できました。
さらにお伺いしたいのですが
TreeView1.FullRowSelect = True
で、幅全体を強調表示に設定した場合は
どのように考えたらいいのでしょうか?

私なりにOffsetなどで調整できないものかと
あれこれやってみましたが
折りたたみのアイコンが消えてしまうなどの
症状が発生してしまい
どうにもならない始末です。

情報の提示不足でお手数をお掛けしますが
よろしくお願いします。
お疲れ様です。

何箇所か苦しい処理があるコードですが、
試してみてください。

Public Class Form_TreeViewTest
    Inherits Form

    Dim WithEvents TreeView1 As New TreeViewEx

    Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
        With Me.TreeView1
            .Dock = DockStyle.Fill
            ._executeFullRowSelect = True
            .DrawMode = TreeViewDrawMode.OwnerDrawAll
            With .Nodes.Add("ノード1")
                .Nodes.Add("子ノード1")
                With .Nodes.Add("子ノード2")
                    .Nodes.Add("孫ノード2−1")
                    .Nodes.Add("孫ノード2−2")
                End With
                .Nodes.Add("子ノード3")
            End With
        End With

        Me.Controls.Add(Me.TreeView1)

        MyBase.OnLoad(e)
    End Sub


    Private Class TreeViewEx
        Inherits TreeView

        Public _executeFullRowSelect As Boolean = False
        Public _selectionForeColor As Color = Color.Blue
        Public _selectionBackColor As Color = Color.Orange

        Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
            Const WM_PAINT As Integer = &HF
            MyBase.WndProc(m)

            ' この辺りは苦しい。
            If m.Msg = WM_PAINT AndAlso Me.DrawMode <> TreeViewDrawMode.Normal Then
                Using g As Graphics = Me.CreateGraphics
                    ' マウス位置のNodeを通常表示に
                    '(クリックしたNodeのText表示の数字部分が若干ずれるのはご了承下さい)
                    Dim info As TreeViewHitTestInfo = Me.HitTest(Me.PointToClient(Cursor.Position))
                    Dim nodeofcursorposition As TreeNode = info.Node
                    If nodeofcursorposition IsNot Nothing Then
                        Me._drawNormalNodeCore(g, nodeofcursorposition)
                    End If

                    ' 選択Nodeのハイライト表示
                    Dim selectednode As TreeNode = Me.SelectedNode
                    Me._drawHighlightedNodeCore(g, selectednode)
                    Dim rowbound As Rectangle = Me._getRowBoundCore(selectednode.Bounds)
                    Me._drawFocusRectangleCore(g, rowbound)
                End Using
            End If
        End Sub

        Protected Overrides Sub OnDrawNode(ByVal e As System.Windows.Forms.DrawTreeNodeEventArgs)
            Dim rowbound As Rectangle = Me._getRowBoundCore(e.Node.Bounds)

            If e.Node.IsSelected Then
                Me._drawBackGroundCore(e.Graphics, rowbound, Me._selectionBackColor)
            End If

            e.DrawDefault = True
            MyBase.OnDrawNode(e)
        End Sub

        Private Function _getRowBoundCore(ByVal nodebound As Rectangle) As Rectangle
            If Me._executeFullRowSelect Then
                Return New Rectangle(0, nodebound.Top, Me.ClientSize.Width, nodebound.Height)
            Else
                Return nodebound
            End If
        End Function

        Private Sub _drawNormalNodeCore(ByVal g As Graphics, ByVal node As TreeNode)
            Me._drawBackGroundCore(g, node.Bounds, Me.BackColor)
            Me._drawTextCore(g, node, Me.ForeColor)
        End Sub

        Private Sub _drawHighlightedNodeCore(ByVal g As Graphics, ByVal node As TreeNode)
            Me._drawBackGroundCore(g, node.Bounds, Me._selectionBackColor)
            Me._drawTextCore(g, node, Me._selectionForeColor)
        End Sub

        Private Sub _drawBackGroundCore(ByVal g As Graphics, _
                                        ByVal bound As Rectangle, ByVal backgroundColor As Color)
            Using backBrush As New SolidBrush(backgroundColor)
                g.FillRectangle(backBrush, bound)
            End Using
        End Sub

        Private Sub _drawTextCore(ByVal g As Graphics, _
                                  ByVal node As TreeNode, ByVal textColor As Color)
            Using textBrush As New SolidBrush(textColor)
                Dim nodeFont As Font = node.NodeFont
                If nodeFont Is Nothing Then
                    nodeFont = Me.Font
                End If
                g.DrawString(node.Text, nodeFont, textBrush, _
                                      node.Bounds.Left, node.Bounds.Top + 1)
            End Using
        End Sub

        ' Focus枠を描画(少し苦しいコード)
        Private Sub _drawFocusRectangleCore(ByVal g As Graphics, _
                                            ByVal bound As Rectangle)
            Using pen As New Pen(Color.Black)
                Dim rect As Rectangle = bound
                Dim points As Point() = {New Point(rect.Left, rect.Top), _
                                         New Point(rect.Right - 1, rect.Top), _
                                         New Point(rect.Right - 1, rect.Bottom - 1), _
                                         New Point(rect.Left, rect.Bottom - 1), _
                                         New Point(rect.Left, rect.Top)}
                g.DrawLines(pen, points)
                pen.Color = SystemColors.ControlLight
                pen.DashStyle = Drawing2D.DashStyle.Dot
                g.DrawLines(pen, points)
            End Using
        End Sub

    End Class
End Class
H.K.R.さん、ありがとうございます。
体調を崩してしまいお返事が遅くなってしまいました。

ご提示いただいたコードの動作を確認しました。
希望どおりで感激しました。
VBの奥の深さをあらためて知ることができました。

また質問させていただくこともあると思いますので
その際はよろしくお願いします。

解決とさせていただきます。
ありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板