OSDN Git Service

event, delegate に対するnullableアノテーションを追加
[opentween/open-tween.git] / OpenTween / DetailsListView.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 //           (c) 2008-2011 Moz (@syo68k)
4 //           (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 //           (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 //           (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 //           (c) 2011      kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
8 // All rights reserved.
9 // 
10 // This file is part of OpenTween.
11 // 
12 // This program is free software; you can redistribute it and/or modify it
13 // under the terms of the GNU General Public License as published by the Free
14 // Software Foundation; either version 3 of the License, or (at your option)
15 // any later version.
16 // 
17 // This program is distributed in the hope that it will be useful, but
18 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
19 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 // for more details. 
21 // 
22 // You should have received a copy of the GNU General Public License along
23 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
24 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
25 // Boston, MA 02110-1301, USA.
26
27 #nullable enable
28
29 using System;
30 using System.Collections.Generic;
31 using System.Linq;
32 using System.Text;
33 using System.Windows.Forms;
34 using System.Drawing;
35 using System.ComponentModel;
36 using System.Runtime.InteropServices;
37 using System.Diagnostics;
38
39 namespace OpenTween.OpenTweenCustomControl
40 {
41     public sealed class DetailsListView : ListView
42     {
43         private Rectangle changeBounds;
44
45         public ContextMenuStrip? ColumnHeaderContextMenuStrip { get; set; }
46
47         public event EventHandler? VScrolled;
48         public event EventHandler? HScrolled;
49
50         public DetailsListView()
51         {
52             View = View.Details;
53             FullRowSelect = true;
54             HideSelection = false;
55             DoubleBuffered = true;
56         }
57
58         /// <summary>
59         /// 複数選択時の起点になるアイテム (selection mark) の位置を取得・設定する
60         /// </summary>
61         /// <remarks>
62         /// Items[idx].Selected の設定では mark が設定されるが、SelectedIndices.Add(idx) では設定されないため、
63         /// 主に後者と合わせて使用する
64         /// </remarks>
65         public int SelectionMark
66         {
67             get => NativeMethods.ListView_GetSelectionMark(this.Handle);
68             set => NativeMethods.ListView_SetSelectionMark(this.Handle, value);
69         }
70
71         public void SelectItems(int[] indices)
72         {
73             foreach (var index in indices)
74             {
75                 if (index < 0 || index >= this.VirtualListSize)
76                     throw new ArgumentOutOfRangeException(nameof(indices));
77
78                 NativeMethods.SelectItem(this, index);
79             }
80
81             this.OnSelectedIndexChanged(EventArgs.Empty);
82         }
83
84         public void SelectAllItems()
85         {
86             NativeMethods.SelectAllItems(this);
87
88             this.OnSelectedIndexChanged(EventArgs.Empty);
89         }
90
91         public void ChangeItemBackColor(ListViewItem item, Color backColor)
92         {
93             if (item.BackColor == backColor)
94                 return;
95
96             item.BackColor = backColor;
97             this.RefreshItemBounds(item);
98         }
99
100         public void ChangeItemForeColor(ListViewItem item, Color foreColor)
101         {
102             if (item.ForeColor == foreColor)
103                 return;
104
105             item.ForeColor = foreColor;
106             this.RefreshItemBounds(item);
107         }
108
109         public void ChangeItemFontAndColor(ListViewItem item, Color foreColor, Font fnt)
110         {
111             if (item.ForeColor == foreColor && item.Font == fnt)
112                 return;
113
114             item.ForeColor = foreColor;
115             item.Font = fnt;
116             this.RefreshItemBounds(item);
117         }
118
119         private void RefreshItemBounds(ListViewItem item)
120         {
121             try
122             {
123                 var itemBounds = item.Bounds;
124                 var drawBounds = Rectangle.Intersect(this.ClientRectangle, itemBounds);
125                 if (drawBounds == Rectangle.Empty)
126                     return;
127
128                 this.changeBounds = drawBounds;
129                 this.Update();
130                 this.changeBounds = Rectangle.Empty;
131             }
132             catch (ArgumentException)
133             {
134                 //タイミングによりBoundsプロパティが取れない?
135                 this.changeBounds = Rectangle.Empty;
136             }
137         }
138
139         [StructLayout(LayoutKind.Sequential)]
140         private struct NMHDR
141         {
142             public IntPtr hwndFrom;
143             public IntPtr idFrom;
144             public int code;
145         }
146
147         [DebuggerStepThrough]
148         protected override void WndProc(ref Message m)
149         {
150             const int WM_ERASEBKGND = 0x14;
151             const int WM_PAINT = 0xF;
152             const int WM_MOUSEWHEEL = 0x20A;
153             const int WM_MOUSEHWHEEL = 0x20E;
154             const int WM_HSCROLL = 0x114;
155             const int WM_VSCROLL = 0x115;
156             const int WM_KEYDOWN = 0x100;
157             const int WM_USER = 0x400;
158             const int WM_REFLECT = WM_USER + 0x1C00;
159             const int WM_NOTIFY = 0x004E;
160             const int WM_CONTEXTMENU = 0x7B;
161             const int LVM_SETITEMCOUNT = 0x102F;
162             const int LVN_ODSTATECHANGED = ((0 - 100) - 15);
163             const long LVSICF_NOSCROLL = 0x2;
164             const long LVSICF_NOINVALIDATEALL = 0x1;
165
166             var hPos = -1;
167             var vPos = -1;
168
169             switch (m.Msg)
170             {
171                 case WM_ERASEBKGND:
172                     if (this.changeBounds != Rectangle.Empty)
173                         m.Msg = 0;
174                     break;
175                 case WM_PAINT:
176                     if (this.changeBounds != Rectangle.Empty)
177                     {
178                         NativeMethods.ValidateRect(this.Handle, IntPtr.Zero);
179                         this.Invalidate(this.changeBounds);
180                         this.changeBounds = Rectangle.Empty;
181                     }
182                     break;
183                 case WM_HSCROLL:
184                     HScrolled?.Invoke(this, EventArgs.Empty);
185                     break;
186                 case WM_VSCROLL:
187                     VScrolled?.Invoke(this, EventArgs.Empty);
188                     break;
189                 case WM_MOUSEWHEEL:
190                 case WM_MOUSEHWHEEL:
191                 case WM_KEYDOWN:
192                     vPos = NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_VERT);
193                     hPos = NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_HORZ);
194                     break;
195                 case WM_CONTEXTMENU:
196                     if (m.WParam != this.Handle)
197                     {
198                         //カラムヘッダメニューを表示
199                         this.ColumnHeaderContextMenuStrip?.Show(new Point(m.LParam.ToInt32()));
200                         return;
201                     }
202                     break;
203                 case LVM_SETITEMCOUNT:
204                     m.LParam = new IntPtr(LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL);
205                     break;
206                 case WM_REFLECT + WM_NOTIFY:
207                     var nmhdr = Marshal.PtrToStructure<NMHDR>(m.LParam);
208
209                     // Ctrl+クリックで選択状態を変更した場合にイベントが発生しない問題への対処
210                     if (nmhdr.code == LVN_ODSTATECHANGED)
211                         this.OnSelectedIndexChanged(EventArgs.Empty);
212                     break;
213             }
214
215             try
216             {
217                 base.WndProc(ref m);
218             }
219             catch (ArgumentOutOfRangeException)
220             {
221                 //Substringでlengthが0以下。アイコンサイズが影響?
222             }
223             catch (AccessViolationException)
224             {
225                 //WndProcのさらに先で発生する。
226             }
227             if (this.IsDisposed) return;
228
229             if (vPos != -1)
230                 if (vPos != NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_VERT))
231                     VScrolled?.Invoke(this, EventArgs.Empty);
232             if (hPos != -1)
233                 if (hPos != NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_HORZ))
234                     HScrolled?.Invoke(this, EventArgs.Empty);
235         }
236    }
237 }