I'm using Winform DataGridView to display images. But when image fills cell, I don't see blue selection or in very low quantity. Please see:
When an cell is selected, I expect make cell whole transparent blue, not just sides or sides which isn't occupied by image. like:
Currently I tried coloring blue myself in paint event but it updates too frequently which hangs software.
I also modify image to look bluish in selection changed event, but again it slows down software.
Is there fix to this ? any workaround or something ? without compromising performance ?
EDIT: This is source code on how I display images on datagridview:
int colms = 4; // total no. of columns in our datagridview
//this create 4 image columns in datagridview
for (int c = 0; c < colms; c )
{
var imgColm = new DataGridViewImageColumn();
imgColm.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
imgColm.ImageLayout = DataGridViewImageCellLayout.Zoom;
grid.Columns.Add(imgColm);
}
int colm = 0;
int row = 0;
//this get all images and display on datagridview
foreach (var img in Directory.GetFiles(@"C:\Users\Administrator\Desktop\images"))
{
if (colm >= colms)
{
row ;
colm = 0;
grid.Rows.Add();
}
((DataGridViewImageCell)grid.Rows[row].Cells[colm]).Value = Thumb.GetThumbnail(img, ThumbSize.LowRes);
colm ;
}
Currently cell painting I use just a workaround,
CodePudding user response:
Here's two examples to test yourself regarding the performance and the fill style of the selected cells. Since your code snippet does not show in what context the code is called, especially creating the image columns part, and to avoid repeating unnecessary routines, use the grid designer to add 4 columns of type DataGridViewImageColumn
and set the auto size and layout properties from there.
Normal Mode
In the Form's ctor, use Reflection
to enable the grid's DoubleBuffered
property to reduce the flicker. The emptyImage
bitmap is the null value of the empty cells.
public partial class SomeForm : Form
{
private Bitmap emptyImage;
public SomeForm()
{
dgv.GetType()
.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue(dgv, true);
emptyImage = new Bitmap(1, 1);
foreach (var col in dgv.Columns.OfType<DataGridViewImageColumn>())
col.DefaultCellStyle.NullValue = emptyImage;
}
Override the OnLoad
method to populate the grid or to call a method for that.
protected override void onl oad(EventArgs e)
{
base.OnLoad(e);
var imgFolder = @"Your-Image-Folder-Path";
LoadImages(imgFolder);
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
emptyImage.Dispose();
}
private void LoadImages(string path)
{
string[] allFiles = Directory.GetFiles(path);
for (int i = 0; i < allFiles.Length; i = 4)
{
var files = allFiles.Skip(i).Take(4);
dgv.Rows.Add(
files.Select(f =>
{
using (var img = Image.FromFile(f, true))
return Thumb.GetThumbnail(img, ThumbSize.LowRes);
}).ToArray());
}
}
Note, your Thumb.GetThumbnail
method returns a new image so you need to dispose of the original image.
Implement the CellPainting
event to draw everything except the DataGridViewPaintParts.SelectionBackground
and fill the selected cells with a semi-transparent color.
private void dgv_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
e.Paint(e.ClipBounds, e.PaintParts & ~DataGridViewPaintParts.SelectionBackground);
if (e.RowIndex >= 0 &&
e.Value != null && e.Value != emptyImage &&
(e.State & DataGridViewElementStates.Selected) > 0)
{
using (var br = new SolidBrush(Color.FromArgb(100, SystemColors.Highlight)))
e.Graphics.FillRectangle(br, e.CellBounds);
}
e.Handled = true;
}
}
Virtual Mode
You need here to have data store to cache only the images you need to display. The images of each cell of the visible rows. For that, The Cache
class is
created to manage the relevant functionalities including:
- Calculating the total number of rows required to display 4 images per visible row. So the
SetMaxRows
method should be called when the grid is first created and resized to recalculate the visible rows. - Loading, creating, and caching the current visible rows images in a
Dictionary<int, Image>
where the keys are the cell numbers. - Passing the requested images when the
CellValueNeeded
event is raised.
public partial class SomeForm : Form
{
private readonly Cache cache;
public SomeForm()
{
dgv.GetType()
.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic)
.SetValue(dgv, true);
dgv.VirtualMode = true;
var imgFolder = @"Your-Image-Folder-Path";
cache = new Cache(imgFolder, dgv.ColumnCount);
dgv.RowCount = cache.GetRowCount();
SetMaxRows();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
cache.Dispose();
}
private void dgv_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e) =>
e.Value = cache.GetImage(e.RowIndex, e.ColumnIndex);
private void dgv_Resize(object sender, EventArgs e) => SetMaxRows();
// Change dgv.RowTemplate.Height as needed...
private void SetMaxRows() =>
cache.MaxRows = (int)Math
.Ceiling((double)dgv.ClientRectangle.Height / dgv.RowTemplate.Height);
private class Cache : IDisposable
{
private readonly Dictionary<int, Image> dict;
private readonly Bitmap nullImage;
private int currentRowIndex = -1;
private Cache()
{
dict = new Dictionary<int, Image>();
nullImage = new Bitmap(1, 1);
}
public Cache(string path, int columnCount) : this()
{
ImageFolder = path;
ColumnCount = columnCount;
}
public string ImageFolder { get; set; }
public int ColumnCount { get; set; }
public int MaxRows { get; set; }
public Bitmap NullImage => nullImage;
public Image GetImage(int rowIndex, int columnIndex)
{
var ri = rowIndex - (rowIndex % MaxRows);
if (ri != currentRowIndex)
{
foreach (var img in dict.Values) img?.Dispose();
currentRowIndex = ri;
dict.Clear();
}
var i = (rowIndex * ColumnCount) columnIndex;
Image res = nullImage;
if (!dict.ContainsKey(i))
{
var file = Directory.EnumerateFiles(ImageFolder)
.Skip(i).FirstOrDefault();
if (file != null)
{
using (var img = Image.FromFile(file, true))
dict[i] = res = Thumb.GetThumbnail(img, ThumbSize.LowRes);
}
}
else
{
res = dict[i];
}
return res;
}
public int GetRowCount()
{
var count = Directory.EnumerateFiles(ImageFolder).Count();
return (int)Math.Ceiling((double)count / ColumnCount);
}
public void Dispose()
{
foreach (var img in dict.Values) img?.Dispose();
nullImage.Dispose();
}
}
Finally, the CellPainting
event remains almost the same except that you get the null image from the cache
instance.
private void dgv_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
e.Paint(e.ClipBounds, e.PaintParts & ~DataGridViewPaintParts.SelectionBackground);
if (e.RowIndex >= 0 &&
e.Value != null && e.Value != cache.NullImage &&
(e.State & DataGridViewElementStates.Selected) > 0)
{
using (var br = new SolidBrush(Color.FromArgb(100, SystemColors.Highlight)))
e.Graphics.FillRectangle(br, e.CellBounds);
}
e.Handled = true;
}
}