/
RatingColumn.cs
340 lines (292 loc) · 12.5 KB
/
RatingColumn.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
//===============================================================================================================
// System : EWSoftware Windows Forms List Controls
// File : RatingColumn.cs
// Author : Eric Woodruff (Eric@EWoodruff.us)
// Updated : 01/04/2023
// Note : Copyright 2007-2023, Eric Woodruff, All rights reserved
//
// This file contains a data grid view column object that contains RatingCell objects
//
// This code is published under the Microsoft Public License (Ms-PL). A copy of the license should be
// distributed with the code and can be found at the project website: https://github.com/EWSoftware/ListControls
// This notice, the author's name, and all copyright notices must remain intact in all applications,
// documentation, and source files.
//
// Date Who Comments
// ==============================================================================================================
// 06/04/2007 EFW Created the code
//===============================================================================================================
using System;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Reflection;
using System.Windows.Forms;
namespace EWSoftware.ListControls.DataGridViewControls
{
/// <summary>
/// This data grid view column contains <see cref="RatingCell"/> objects
/// </summary>
[ToolboxBitmap(typeof(RatingColumn), "RatingColumn.bmp")]
public class RatingColumn : DataGridViewImageColumn
{
#region Private data members
//=====================================================================
// The path to the embedded resources
private const string ResourcePath = "EWSoftware.ListControls.Images.";
// The internal image list
private static readonly ImageList ilStars;
// The cell image and custom image list
private Bitmap cellImage;
private ImageList images;
private int maxRating;
private Cursor originalCursor;
#endregion
#region Properties
//=====================================================================
/// <summary>
/// This property is not used and is hidden in the designer
/// </summary>
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
EditorBrowsable(EditorBrowsableState.Never)]
public new Image Image
{
get => base.Image;
set => base.Image = value;
}
/// <summary>
/// This property is not used and is hidden in the designer
/// </summary>
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
EditorBrowsable(EditorBrowsableState.Never)]
public new DataGridViewImageCellLayout ImageLayout
{
get => base.ImageLayout;
set { }
}
/// <summary>
/// This gets or sets the maximum rating and thus the of images drawn in the cells
/// </summary>
/// <value>The default is five.</value>
[Category("Appearance"), DefaultValue(5), Description("The maximum rating and thus images drawn in the cells")]
public int MaximumRating
{
get => maxRating;
set
{
maxRating = value;
cellImage?.Dispose();
cellImage = null;
if(this.DataGridView != null)
DataGridViewHelper.OnColumnCommonChange(this.DataGridView, this.Index);
}
}
/// <summary>
/// This is used to get or set the image list used for the column's cells
/// </summary>
/// <value>If not set, a default set of star images is used. If set, the image list should contain
/// three images. The first is for an empty/unused image, the second is for a filled/used image, and
/// the third is for a hot image drawn when the mouse is over the cell images.</value>
[Category("Behavior"), DefaultValue(null), Description("The image list to use for the column")]
public ImageList ImageList
{
get => images;
set
{
if(images != value)
{
EventHandler recreateHandle = this.ImageList_RecreateHandle;
EventHandler disposeList = this.ImageList_Disposed;
if(images != null)
{
images.RecreateHandle -= recreateHandle;
images.Disposed -= disposeList;
}
images = value;
cellImage?.Dispose();
cellImage = null;
if(value != null)
{
images.RecreateHandle += recreateHandle;
images.Disposed += disposeList;
}
if(this.DataGridView != null)
DataGridViewHelper.OnColumnCommonChange(this.DataGridView, this.Index);
}
}
}
#endregion
#region Events
//=====================================================================
/// <summary>
/// This event is raised when a cell needs to map a cell value to a rating
/// </summary>
[Category("Behavior"), Description("Occurs when a cell needs to map a cell value to a rating")]
public event EventHandler<MapRatingEventArgs> MapValueToRating;
/// <summary>
/// This raises the <see cref="MapValueToRating"/> event
/// </summary>
/// <param name="e">The event arguments</param>
protected internal virtual void OnMapValueToRating(MapRatingEventArgs e)
{
MapValueToRating?.Invoke(this, e);
}
/// <summary>
/// This event is raised when a cell needs to map a rating to a cell value
/// </summary>
[Category("Behavior"), Description("Occurs when a cell needs to map a rating to a cell value")]
public event EventHandler<MapRatingEventArgs> MapRatingToValue;
/// <summary>
/// This raises the <see cref="MapRatingToValue"/> event
/// </summary>
/// <param name="e">The event arguments</param>
protected internal virtual void OnMapRatingToValue(MapRatingEventArgs e)
{
MapRatingToValue?.Invoke(this, e);
}
#endregion
#region Private methods
//=====================================================================
/// <summary>
/// Static constructor. This loads the default images.
/// </summary>
static RatingColumn()
{
Assembly asm = Assembly.GetExecutingAssembly();
ilStars = new ImageList { ImageSize = new Size(14, 14) };
ilStars.Images.Add(new Bitmap(asm.GetManifestResourceStream(ResourcePath + "RatingStarEmpty.png")),
Color.Magenta);
ilStars.Images.Add(new Bitmap(asm.GetManifestResourceStream(ResourcePath + "RatingStarFilled.png")),
Color.Magenta);
ilStars.Images.Add(new Bitmap(asm.GetManifestResourceStream(ResourcePath + "RatingStarHot.png")),
Color.Magenta);
}
/// <summary>
/// This returns the original data grid view cursor
/// </summary>
/// <remarks>This has to be at the column level as cells don't always get the correct cursor due to the
/// way they are managed internally by the grid.</remarks>
internal Cursor OriginalCursor
{
get
{
if(originalCursor == null && base.DataGridView != null)
originalCursor = base.DataGridView.Cursor;
return originalCursor ?? Cursors.Default;
}
}
/// <summary>
/// Return the user-supplied image list if not null or the default image list if it is null
/// </summary>
internal ImageList ImageListInternal => images ?? ilStars;
/// <summary>
/// Invalidate the column when the image list handle is recreated
/// </summary>
/// <param name="sender">The sender of the event</param>
/// <param name="e">The event arguments</param>
private void ImageList_RecreateHandle(object sender, EventArgs e)
{
this.DataGridView?.InvalidateColumn(this.Index);
}
/// <summary>
/// Detach the image list when it is disposed
/// </summary>
/// <param name="sender">The sender of the event</param>
/// <param name="e">The event arguments</param>
private void ImageList_Disposed(object sender, EventArgs e)
{
this.ImageList = null;
}
#endregion
#region Constructor
//=====================================================================
/// <summary>
/// Constructor
/// </summary>
/// <remarks>The value type is <see cref="Object"/>. 16-bit and 32-bit integer values are assumed to be
/// actual rating values between zero and <see cref="MaximumRating" /> unless mapped to a different
/// rating value. All other types must be mapped to a rating using the <see cref="MapValueToRating"/>
/// event.</remarks>
public RatingColumn()
{
base.ValueType = typeof(object);
base.CellTemplate = new RatingCell();
maxRating = 5;
}
#endregion
#region Methods
//=====================================================================
/// <summary>
/// This is used to clone the column
/// </summary>
/// <returns>A clone of the column</returns>
public override object Clone()
{
RatingColumn clone = (RatingColumn)base.Clone();
clone.ImageList = images;
clone.MaximumRating = maxRating;
return clone;
}
/// <summary>
/// Convert the column to its string description
/// </summary>
/// <returns>A string description of the column</returns>
public override string ToString()
{
return $"RatingColumn {{ Name={this.Name}, Index={this.Index} }}";
}
/// <summary>
/// This is used to draw the cell image on behalf of the cell
/// </summary>
/// <param name="value">The current cell value</param>
/// <param name="rowIndex">The cell's row index</param>
/// <param name="mouseIndex">The index under the mouse or -1 if the mouse isn't over an index</param>
/// <returns>The image to display or null if there is no image</returns>
protected internal Image DrawImage(object value, int rowIndex, int mouseIndex)
{
ImageList imageList = this.ImageListInternal;
int idx, imageIdx, x = 0, cellValue = 0, width = imageList.ImageSize.Width;
// Create the cell bitmap on first use
if(cellImage == null)
cellImage = new Bitmap(width * maxRating, imageList.ImageSize.Height);
if(value is int iv)
cellValue = iv;
else
{
if(value is short sv)
cellValue = sv;
}
// Let the user map the value to a rating
MapRatingEventArgs mapArgs = new MapRatingEventArgs(base.Index, rowIndex, value, cellValue);
this.OnMapValueToRating(mapArgs);
cellValue = mapArgs.Rating;
if(cellValue < 0)
cellValue = 0;
else
if(cellValue > maxRating)
cellValue = maxRating;
using(Graphics g = Graphics.FromImage(cellImage))
{
// Draw each image using the appropriate state
for(idx = 0; idx < maxRating; idx++, x += width)
{
if(mouseIndex != -1)
{
if(idx <= mouseIndex)
imageIdx = 2; // Hot
else
imageIdx = 0; // Empty
}
else
if(idx < cellValue)
imageIdx = 1; // Filled
else
imageIdx = 0; // Empty
g.DrawImage(imageList.Images[imageIdx], x, 0);
}
}
return cellImage;
}
#endregion
}
}