OSDN Git Service

655e8bdce97fede1716b16d0adeecfbf65a355f1
[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         //[System.ComponentModel.DefaultValue(0),
59         // System.ComponentModel.RefreshProperties(System.ComponentModel.RefreshProperties.Repaint)]
60         //public new int VirtualListSize
61         //{
62         //    get { return base.VirtualListSize; }
63         //    set
64         //    {
65         //        if (value == base.VirtualListSize) return;
66         //        if (base.VirtualListSize > 0 && value > 0)
67         //        {
68         //            int topIndex = 0;
69         //            if (!this.IsDisposed)
70         //            {
71         //                if (base.VirtualListSize < value)
72         //                {
73         //                    if (this.TopItem == null)
74         //                    {
75         //                        topIndex = 0;
76         //                    }
77         //                    else
78         //                    {
79         //                        topIndex = this.TopItem.Index;
80         //                    }
81         //                    topIndex = Math.Min(topIndex, Math.Abs(value - 1));
82         //                    this.TopItem = this.Items[topIndex];
83         //                }
84         //                else
85         //                {
86         //                    if (this.TopItem == null)
87         //                    {
88         //                        topIndex = 0;
89         //                    }
90         //                    else
91         //                    {
92         //
93         //                    }
94         //                    this.TopItem = this.Items[0];
95         //                }
96         //            }
97         //        }
98         //        base.VirtualListSize = value;
99         //    }
100         //}
101
102         /// <summary>
103         /// 複数選択時の起点になるアイテム (selection mark) の位置を取得・設定する
104         /// </summary>
105         /// <remarks>
106         /// Items[idx].Selected の設定では mark が設定されるが、SelectedIndices.Add(idx) では設定されないため、
107         /// 主に後者と合わせて使用する
108         /// </remarks>
109         public int SelectionMark
110         {
111             get => NativeMethods.ListView_GetSelectionMark(this.Handle);
112             set => NativeMethods.ListView_SetSelectionMark(this.Handle, value);
113         }
114
115         public void SelectItems(int[] indices)
116         {
117             foreach (var index in indices)
118             {
119                 if (index < 0 || index >= this.VirtualListSize)
120                     throw new ArgumentOutOfRangeException(nameof(indices));
121
122                 NativeMethods.SelectItem(this, index);
123             }
124
125             this.OnSelectedIndexChanged(EventArgs.Empty);
126         }
127
128         public void SelectAllItems()
129         {
130             NativeMethods.SelectAllItems(this);
131
132             this.OnSelectedIndexChanged(EventArgs.Empty);
133         }
134
135         public void ChangeItemBackColor(ListViewItem item, Color backColor)
136         {
137             if (item.BackColor == backColor)
138                 return;
139
140             item.BackColor = backColor;
141             this.RefreshItemBounds(item);
142         }
143
144         public void ChangeItemForeColor(ListViewItem item, Color foreColor)
145         {
146             if (item.ForeColor == foreColor)
147                 return;
148
149             item.ForeColor = foreColor;
150             this.RefreshItemBounds(item);
151         }
152
153         public void ChangeItemFontAndColor(ListViewItem item, Color foreColor, Font fnt)
154         {
155             if (item.ForeColor == foreColor && item.Font == fnt)
156                 return;
157
158             item.ForeColor = foreColor;
159             item.Font = fnt;
160             this.RefreshItemBounds(item);
161         }
162
163         private void RefreshItemBounds(ListViewItem item)
164         {
165             try
166             {
167                 var itemBounds = item.Bounds;
168                 var drawBounds = Rectangle.Intersect(this.ClientRectangle, itemBounds);
169                 if (drawBounds == Rectangle.Empty)
170                     return;
171
172                 this.changeBounds = drawBounds;
173                 this.Update();
174                 this.changeBounds = Rectangle.Empty;
175             }
176             catch (ArgumentException)
177             {
178                 //タイミングによりBoundsプロパティが取れない?
179                 this.changeBounds = Rectangle.Empty;
180             }
181         }
182
183         [StructLayout(LayoutKind.Sequential)]
184         private struct NMHDR
185         {
186             public IntPtr hwndFrom;
187             public IntPtr idFrom;
188             public int code;
189         }
190
191         [DebuggerStepThrough]
192         protected override void WndProc(ref Message m)
193         {
194             const int WM_ERASEBKGND = 0x14;
195             const int WM_PAINT = 0xF;
196             const int WM_MOUSEWHEEL = 0x20A;
197             const int WM_MOUSEHWHEEL = 0x20E;
198             const int WM_HSCROLL = 0x114;
199             const int WM_VSCROLL = 0x115;
200             const int WM_KEYDOWN = 0x100;
201             const int WM_USER = 0x400;
202             const int WM_REFLECT = WM_USER + 0x1C00;
203             const int WM_NOTIFY = 0x004E;
204             const int WM_CONTEXTMENU = 0x7B;
205             const int LVM_SETITEMCOUNT = 0x102F;
206             const int LVN_ODSTATECHANGED = ((0 - 100) - 15);
207             const long LVSICF_NOSCROLL = 0x2;
208             const long LVSICF_NOINVALIDATEALL = 0x1;
209
210             var hPos = -1;
211             var vPos = -1;
212
213             switch (m.Msg)
214             {
215                 case WM_ERASEBKGND:
216                     if (this.changeBounds != Rectangle.Empty)
217                         m.Msg = 0;
218                     break;
219                 case WM_PAINT:
220                     if (this.changeBounds != Rectangle.Empty)
221                     {
222                         NativeMethods.ValidateRect(this.Handle, IntPtr.Zero);
223                         this.Invalidate(this.changeBounds);
224                         this.changeBounds = Rectangle.Empty;
225                     }
226                     break;
227                 case WM_HSCROLL:
228                     HScrolled?.Invoke(this, EventArgs.Empty);
229                     break;
230                 case WM_VSCROLL:
231                     VScrolled?.Invoke(this, EventArgs.Empty);
232                     break;
233                 case WM_MOUSEWHEEL:
234                 case WM_MOUSEHWHEEL:
235                 case WM_KEYDOWN:
236                     vPos = NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_VERT);
237                     hPos = NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_HORZ);
238                     break;
239                 case WM_CONTEXTMENU:
240                     if (m.WParam != this.Handle)
241                     {
242                         //カラムヘッダメニューを表示
243                         this.ColumnHeaderContextMenuStrip?.Show(new Point(m.LParam.ToInt32()));
244                         return;
245                     }
246                     break;
247                 case LVM_SETITEMCOUNT:
248                     m.LParam = new IntPtr(LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL);
249                     break;
250                 case WM_REFLECT + WM_NOTIFY:
251                     var nmhdr = Marshal.PtrToStructure<NMHDR>(m.LParam);
252
253                     // Ctrl+クリックで選択状態を変更した場合にイベントが発生しない問題への対処
254                     if (nmhdr.code == LVN_ODSTATECHANGED)
255                         this.OnSelectedIndexChanged(EventArgs.Empty);
256                     break;
257             }
258
259             try
260             {
261                 base.WndProc(ref m);
262             }
263             catch (ArgumentOutOfRangeException)
264             {
265                 //Substringでlengthが0以下。アイコンサイズが影響?
266             }
267             catch (AccessViolationException)
268             {
269                 //WndProcのさらに先で発生する。
270             }
271             if (this.IsDisposed) return;
272
273             if (vPos != -1)
274                 if (vPos != NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_VERT))
275                     VScrolled?.Invoke(this, EventArgs.Empty);
276             if (hPos != -1)
277                 if (hPos != NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_HORZ))
278                     HScrolled?.Invoke(this, EventArgs.Empty);
279         }
280    }
281 }