OSDN Git Service

Ctrl+クリックで選択状態を変更した場合にTabModel.SelectedStatusIdが更新されない問題を修正
[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 using System;
28 using System.Collections.Generic;
29 using System.Linq;
30 using System.Text;
31 using System.Windows.Forms;
32 using System.Drawing;
33 using System.ComponentModel;
34 using System.Runtime.InteropServices;
35 using System.Diagnostics;
36
37 namespace OpenTween.OpenTweenCustomControl
38 {
39     public sealed class DetailsListView : ListView
40     {
41         private Rectangle changeBounds;
42
43         public ContextMenuStrip ColumnHeaderContextMenuStrip { get; set; }
44
45         public event EventHandler VScrolled;
46         public event EventHandler HScrolled;
47
48         public DetailsListView()
49         {
50             View = View.Details;
51             FullRowSelect = true;
52             HideSelection = false;
53             DoubleBuffered = true;
54         }
55
56         //[System.ComponentModel.DefaultValue(0),
57         // System.ComponentModel.RefreshProperties(System.ComponentModel.RefreshProperties.Repaint)]
58         //public new int VirtualListSize
59         //{
60         //    get { return base.VirtualListSize; }
61         //    set
62         //    {
63         //        if (value == base.VirtualListSize) return;
64         //        if (base.VirtualListSize > 0 && value > 0)
65         //        {
66         //            int topIndex = 0;
67         //            if (!this.IsDisposed)
68         //            {
69         //                if (base.VirtualListSize < value)
70         //                {
71         //                    if (this.TopItem == null)
72         //                    {
73         //                        topIndex = 0;
74         //                    }
75         //                    else
76         //                    {
77         //                        topIndex = this.TopItem.Index;
78         //                    }
79         //                    topIndex = Math.Min(topIndex, Math.Abs(value - 1));
80         //                    this.TopItem = this.Items[topIndex];
81         //                }
82         //                else
83         //                {
84         //                    if (this.TopItem == null)
85         //                    {
86         //                        topIndex = 0;
87         //                    }
88         //                    else
89         //                    {
90         //
91         //                    }
92         //                    this.TopItem = this.Items[0];
93         //                }
94         //            }
95         //        }
96         //        base.VirtualListSize = value;
97         //    }
98         //}
99
100         /// <summary>
101         /// 複数選択時の起点になるアイテム (selection mark) の位置を取得・設定する
102         /// </summary>
103         /// <remarks>
104         /// Items[idx].Selected の設定では mark が設定されるが、SelectedIndices.Add(idx) では設定されないため、
105         /// 主に後者と合わせて使用する
106         /// </remarks>
107         public int SelectionMark
108         {
109             get => NativeMethods.ListView_GetSelectionMark(this.Handle);
110             set => NativeMethods.ListView_SetSelectionMark(this.Handle, value);
111         }
112
113         public void SelectItems(int[] indices)
114         {
115             foreach (var index in indices)
116             {
117                 if (index < 0 || index >= this.VirtualListSize)
118                     throw new ArgumentOutOfRangeException(nameof(indices));
119
120                 NativeMethods.SelectItem(this, index);
121             }
122
123             this.OnSelectedIndexChanged(EventArgs.Empty);
124         }
125
126         public void SelectAllItems()
127         {
128             NativeMethods.SelectAllItems(this);
129
130             this.OnSelectedIndexChanged(EventArgs.Empty);
131         }
132
133         public void ChangeItemBackColor(int index, Color backColor)
134             => this.ChangeSubItemBackColor(index, 0, backColor);
135
136         public void ChangeItemForeColor(int index, Color foreColor)
137             => this.ChangeSubItemForeColor(index, 0, foreColor);
138
139         public void ChangeItemFont(int index, Font fnt)
140             => this.ChangeSubItemFont(index, 0, fnt);
141
142         public void ChangeItemFontAndColor(int index, Color foreColor, Font fnt)
143             => this.ChangeSubItemStyles(index, 0, BackColor, foreColor, fnt);
144
145         public void ChangeItemStyles(int index, Color backColor, Color foreColor, Font fnt)
146             => this.ChangeSubItemStyles(index, 0, backColor, foreColor, fnt);
147
148         public void ChangeSubItemBackColor(int itemIndex, int subitemIndex, Color backColor)
149         {
150             var item = this.Items[itemIndex];
151             item.SubItems[subitemIndex].BackColor = backColor;
152             SetUpdateBounds(item, subitemIndex);
153             this.Update();
154             this.changeBounds = Rectangle.Empty;
155         }
156
157         public void ChangeSubItemForeColor(int itemIndex, int subitemIndex, Color foreColor)
158         {
159             var item = this.Items[itemIndex];
160             item.SubItems[subitemIndex].ForeColor = foreColor;
161             SetUpdateBounds(item, subitemIndex);
162             this.Update();
163             this.changeBounds = Rectangle.Empty;
164         }
165
166         public void ChangeSubItemFont(int itemIndex, int subitemIndex, Font fnt)
167         {
168             var item = this.Items[itemIndex];
169             item.SubItems[subitemIndex].Font = fnt;
170             SetUpdateBounds(item, subitemIndex);
171             this.Update();
172             this.changeBounds = Rectangle.Empty;
173         }
174
175         public void ChangeSubItemFontAndColor(int itemIndex, int subitemIndex, Color foreColor, Font fnt)
176         {
177             var item = this.Items[itemIndex];
178             var subItem = item.SubItems[subitemIndex];
179             subItem.ForeColor = foreColor;
180             subItem.Font = fnt;
181             SetUpdateBounds(item, subitemIndex);
182             this.Update();
183             this.changeBounds = Rectangle.Empty;
184         }
185
186         public void ChangeSubItemStyles(int itemIndex, int subitemIndex, Color backColor, Color foreColor, Font fnt)
187         {
188             var item = this.Items[itemIndex];
189             var subItem = item.SubItems[subitemIndex];
190             subItem.BackColor = backColor;
191             subItem.ForeColor = foreColor;
192             subItem.Font = fnt;
193             SetUpdateBounds(item, subitemIndex);
194             this.Update();
195             this.changeBounds = Rectangle.Empty;
196         }
197
198         private void SetUpdateBounds(ListViewItem item, int subItemIndex)
199         {
200             try
201             {
202                 if (subItemIndex > this.Columns.Count)
203                 {
204                     throw new ArgumentOutOfRangeException(nameof(subItemIndex));
205                 }
206                 if (item.UseItemStyleForSubItems)
207                 {
208                     this.changeBounds = item.Bounds;
209                 }
210                 else
211                 {
212                     this.changeBounds = this.GetSubItemBounds(item, subItemIndex);
213                 }
214             }
215             catch (ArgumentException)
216             {
217                 //タイミングによりBoundsプロパティが取れない?
218                 this.changeBounds = Rectangle.Empty;
219             }
220         }
221
222         private Rectangle GetSubItemBounds(ListViewItem item, int subitemIndex)
223         {
224             if (subitemIndex == 0 && this.Columns.Count > 0)
225             {
226                 Rectangle col0 = item.Bounds;
227                 return new Rectangle(col0.Left, col0.Top, item.SubItems[1].Bounds.X + 1, col0.Height);
228             }
229             else
230             {
231                 return item.SubItems[subitemIndex].Bounds;
232             }
233         }
234
235         [StructLayout(LayoutKind.Sequential)]
236         private struct NMHDR
237         {
238             public IntPtr hwndFrom;
239             public IntPtr idFrom;
240             public int code;
241         }
242
243         [DebuggerStepThrough]
244         protected override void WndProc(ref Message m)
245         {
246             const int WM_ERASEBKGND = 0x14;
247             const int WM_PAINT = 0xF;
248             const int WM_MOUSEWHEEL = 0x20A;
249             const int WM_MOUSEHWHEEL = 0x20E;
250             const int WM_HSCROLL = 0x114;
251             const int WM_VSCROLL = 0x115;
252             const int WM_KEYDOWN = 0x100;
253             const int WM_USER = 0x400;
254             const int WM_REFLECT = WM_USER + 0x1C00;
255             const int WM_NOTIFY = 0x004E;
256             const int WM_CONTEXTMENU = 0x7B;
257             const int LVM_SETITEMCOUNT = 0x102F;
258             const int LVN_ODSTATECHANGED = ((0 - 100) - 15);
259             const long LVSICF_NOSCROLL = 0x2;
260             const long LVSICF_NOINVALIDATEALL = 0x1;
261
262             int hPos = -1;
263             int vPos = -1;
264
265             switch (m.Msg)
266             {
267                 case WM_ERASEBKGND:
268                     if (this.changeBounds != Rectangle.Empty)
269                         m.Msg = 0;
270                     break;
271                 case WM_PAINT:
272                     if (this.changeBounds != Rectangle.Empty)
273                     {
274                         NativeMethods.ValidateRect(this.Handle, IntPtr.Zero);
275                         this.Invalidate(this.changeBounds);
276                         this.changeBounds = Rectangle.Empty;
277                     }
278                     break;
279                 case WM_HSCROLL:
280                     HScrolled?.Invoke(this, EventArgs.Empty);
281                     break;
282                 case WM_VSCROLL:
283                     VScrolled?.Invoke(this, EventArgs.Empty);
284                     break;
285                 case WM_MOUSEWHEEL:
286                 case WM_MOUSEHWHEEL:
287                 case WM_KEYDOWN:
288                     vPos = NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_VERT);
289                     hPos = NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_HORZ);
290                     break;
291                 case WM_CONTEXTMENU:
292                     if (m.WParam != this.Handle)
293                     {
294                         //カラムヘッダメニューを表示
295                         this.ColumnHeaderContextMenuStrip?.Show(new Point(m.LParam.ToInt32()));
296                         return;
297                     }
298                     break;
299                 case LVM_SETITEMCOUNT:
300                     m.LParam = new IntPtr(LVSICF_NOSCROLL | LVSICF_NOINVALIDATEALL);
301                     break;
302                 case WM_REFLECT + WM_NOTIFY:
303                     var nmhdr = Marshal.PtrToStructure<NMHDR>(m.LParam);
304
305                     // Ctrl+クリックで選択状態を変更した場合にイベントが発生しない問題への対処
306                     if (nmhdr.code == LVN_ODSTATECHANGED)
307                         this.OnSelectedIndexChanged(EventArgs.Empty);
308                     break;
309             }
310
311             try
312             {
313                 base.WndProc(ref m);
314             }
315             catch (ArgumentOutOfRangeException)
316             {
317                 //Substringでlengthが0以下。アイコンサイズが影響?
318             }
319             catch (AccessViolationException)
320             {
321                 //WndProcのさらに先で発生する。
322             }
323             if (this.IsDisposed) return;
324
325             if (vPos != -1)
326                 if (vPos != NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_VERT))
327                     VScrolled?.Invoke(this, EventArgs.Empty);
328             if (hPos != -1)
329                 if (hPos != NativeMethods.GetScrollPosition(this, NativeMethods.ScrollBarDirection.SB_HORZ))
330                     HScrolled?.Invoke(this, EventArgs.Empty);
331         }
332    }
333 }