I have a form that contains a picturebox. When the form loads the default image loads fine. I then update the image when something in my form changes that changes the image being displayed. The generation of this image works fine also, I can see the image on disk and open it with paint, etc. In general what I do is open a file stream on the image location and then set the image to this location.
if (this.picPreview.Image != null)
{
this.picPreview.Image.Dispose();
this.picPreview.Image = null;
}
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
this.picPreview.Image = System.Drawing.Image.FromStream(fs);
But no matter what I do the image appears blank on the form. I've tried refreshing the form, refreshing the picturebox control, setting it's visible property to visible, nothing helps.
I create a separate form which contains only a picturebox and pass the image location to the form and repeat the process of opening the stream and then setting the image to this location it works perfectly.
No exceptions are being throw AFAIK...debugger is set to break on all exceptions.
What might cause this behavior?
Any advice is appreciated. I have another application that does image generation in a backgroundworker thread and that works fine also.
Maybe providing more background about what I'm trying to do will help me get to the bottom of this. For every row in my datagridview there are between one and three columns that have an image associated with them. I've generated all these images in advance. Scrolling through the grid I get a preview image of the first image column in my picture box using the SelectionChanged event. It works perfectly. I also have cells that when clicked on show a form window with a preview of the images which make up the image on the main form. This also works perfectly. I can change rows and click on cells in the grid and everything works. Basically I'm constructing a new image based on what the user selects on other controls which are bound to the datagrid.
The problem arises when I attempt to change the image in the picture box on the main form. I can update the datasource, and see the grid values update, but the image, which I now regenerate using third party software, which I can verify is on the disk and is viewable after the update takes place, just vanishes. Once this happens, I no longer get an image in the picture box in the form until I close the form and reopen, and then all the updated data is there and everything works again. The code that gets called on selection changed to set the image is exactly the same as the code in place to update the new image. It is completely synchronous. I'm running out of ideas other than starting from scratch with a whole new form.
Thanks again for all suggestions.
I'll start from the top. The overall flow is as follows:
Open a form which contains a datagrid which is bound to a SQL view. The dgv is read-only, and only one row can be selected at a time. The view populates automatically, along with controls which are bound to each column of the grid. These include text boxes for some, comboboxes and checkboxes for the others. Each row has a set of images associated with it. When the form loads, I can scroll down the view and for each row a new image appears in the picture box on the form. All these images have been pregenerated. When a row is selected, there may be up to three images for the row, in which case navigation buttons are enabled to allow the user to preview each image.
I select a row, change the controls on the form to change one or more cell values in the selected row. I then hit a save button, which updates the database and the corresponding data in the grid. I then attempt to update the image(s) for this row. At this point the picture box disappears and I lose the previews on the form all together; no images appear until I close and reopen the form, and all is well again until I do a save.
In trying to solve this I find that updating a bound dgv causes the selectchanged event to be raised multiple times. There is code to handle cases where the binding is not complete, or there is nothing selected in the view. There is also code in the btnSave_Click handler to suspend the selectionchanged event handler until the update is complete and the image(s) are regenerated. Despite doing this, even though the row I updated is selected in the view, the actually selected row (where the arrow is positioned, and what all the controls display) the first row is always the "current" row after the update. I don't know how to fix that yet. Here is the code for the selection changed and the button save event handlers.
Here is a screen shot of the form:
And the code for the selectionchanged and btn_save event handlers:
/// <summary>
/// update the preview on a row selection change
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
if (!BindingComplete) return;
DataGridView dgv = (DataGridView)sender;
if (!dgv.Focused || dgv.CurrentRow == null) return;
// set the pic preview to the current row image(s)
// we need the record for the current index
DataRowView currentDataRowView = (DataRowView)dgv.CurrentRow.DataBoundItem;
if (currentDataRowView == null) return;
DataRow currentRow = currentDataRowView.Row;
LastSelectedIndex = dgv.SelectedRows[0].Index;
Debug.WriteLine("Current row in SelectionChanged: " + currentRow.ItemArray[0].ToString());
bool showBox = false, showProd = false, showWire = false;
string box, prod, wire;
string pdcProductName = currentRow.ItemArray[0].ToString();
showWire = !string.IsNullOrEmpty(wire = currentRow.ItemArray[7].ToString());
showBox = !string.IsNullOrEmpty(box = currentRow.ItemArray[8].ToString());
showProd = !string.IsNullOrEmpty(prod = currentRow.ItemArray[9].ToString());
// check for wirepath, box, and product. Enable the nav buttons if there is more than
// one label for this product. We need to check for LabelFileName being the same for both
// box and product, in which case there is one file for both which defaults to box
if ((showBox && showProd && prod == box) || showBox)
{
string targetFile = PreviewImagePath + pdcProductName + "_eBox.png";
if (picPreview.Image != null)
{
//picPreview.Image.Dispose();
//picPreview.Image = null;
}
// if the preview image doesn't exist yet use a default image
if (!File.Exists(targetFile))
{
// make the loading gif invisible
this.picLoading.Visible = true;
//picPreview.Image = AdminTILE.Properties.Resources.StandardPaper;
}
else
{
this.picLoading.Visible = false;
Debug.WriteLine("Opening file " + targetFile);
FileStream fs = new FileStream(targetFile, FileMode.Open, FileAccess.Read);
picPreview.Image = System.Drawing.Image.FromStream(fs);
Image imgCopy = (Image)picPreview.Image.Clone();
this.picPreview.Visible = true;
fs.Close();
// preview in another frame
if (frm.IsDisposed)
{
frm = new PreviewImage();
}
frm.PreviewLabel(imgCopy);
frm.Show();
//picPreview.ImageLocation = targetFile;
}
}
else if (showProd)
{
string targetFile = PreviewImagePath + pdcProductName + "_eBox.png";
if (picPreview.Image != null)
{
picPreview.Image.Dispose();
//picPreview.Image = null;
}
if (!File.Exists(targetFile))
{
// make the loading gif invisible
this.picLoading.Visible = true;
//picPreview.Image = AdminTILE.Properties.Resources.StandardPaper;
}
else
{
this.picLoading.Visible = false;
FileStream fs = new FileStream(targetFile, FileMode.Open, FileAccess.Read);
picPreview.Image = System.Drawing.Image.FromStream(fs);
fs.Close();
}
}
}
/// <summary>
/// update the database with the current selections
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSave_Click(object sender, EventArgs e)
{
if (dataGridView1.SelectedRows.Count == 0)
{
MessageBox.Show("No record is selected to update");
return;
}
DialogResult result1 = MessageBox.Show("Saving Label Configuration. Are you sure?",
"IMPORTANT!", MessageBoxButtons.YesNoCancel);
// update the view
if (result1 == DialogResult.Yes)
{
// we need the record for the current index
DataRowView currentDataRowView = (DataRowView)dataGridView1.CurrentRow.DataBoundItem;
DataRow currentRow = currentDataRowView.Row;
string pdcProductName = currentRow.ItemArray[0].ToString();
Int32 currentIndex = dataGridView1.SelectedRows[0].Index;
Debug.WriteLine("Current index in Save:" + currentIndex.ToString());
string AgencyId="", LogoId="", WireId="";
SqlDataAdapter LabeledProductsDataTableAdapter =
new SqlDataAdapter("SELECT * FROM LabeledProducts",
printConfigTableAdapter.Connection);
SqlDataAdapter LogosDataTableAdapter =
new SqlDataAdapter("SELECT * FROM Logos",
printConfigTableAdapter.Connection);
if (vwTILEAdminTableAdapter.Connection.State != ConnectionState.Open)
{
printConfigTableAdapter.Connection.Open();
}
DataTable LogoDataTable = new DataTable();
LogosDataTableAdapter.Fill(LogoDataTable);
DataTable LabeledProductsDataTable = new DataTable();
LabeledProductsDataTableAdapter.Fill(LabeledProductsDataTable);
StringBuilder sql = new StringBuilder();
// Fill a table with the results of the
// data adapter and query the table instead of the database.
// An empty LogoDescription maps to an empty filename
DataRow dataRow;
if (cbAgency.SelectedItem != null)
{
sql.Append("LogoDescription = '").Append(cbAgency.SelectedItem).Append("'");
dataRow = LogoDataTable.Select(sql.ToString())[0];
AgencyId = dataRow.ItemArray[0].ToString();
sql.Clear();
}
if (cbPrivateLabel.SelectedItem != null)
{
sql.Append("LogoDescription = '").Append(cbPrivateLabel.SelectedItem).Append("'");
dataRow = LogoDataTable.Select(sql.ToString())[0];
LogoId = dataRow.ItemArray[0].ToString();
sql.Clear();
}
if (cbWire.SelectedItem != null)
{
sql.Append("LogoDescription = '").Append(cbWire.SelectedItem).Append("'");
dataRow = LogoDataTable.Select(sql.ToString())[0];
WireId = dataRow.ItemArray[0].ToString();
sql.Clear();
}
// PdcProductName is the primary key
sql.Append(@"UPDATE [dbo].[LabeledProducts]
SET [PdcProductName] = @pdcProd
,[LabelProductName] = @lblProd
,[LabelDescription] = @lblDesc
,[Power] = @pwr
,[Fabrication] = 0
,[UL_File_Number] = @ul
,[PrePrintedSerial] = @pps
,[ShowOrderOnLabel] = 0
,[PrivateLabelLogoId] = @plid
,[AgencyImageId] = @aid
,[WireDiagConfigId] = @wid
,[ReleasedForProduction] = @rfp
WHERE PdcProductName = '").Append(pdcProductName).Append("'");
using (SqlCommand command = new SqlCommand(sql.ToString(), vwTILEAdminTableAdapter.Connection))
{
if (vwTILEAdminTableAdapter.Connection.State != ConnectionState.Open)
vwTILEAdminTableAdapter.Connection.Open();
LabeledProductsDataTableAdapter.UpdateCommand = command;
LabeledProductsDataTableAdapter.UpdateCommand.Parameters.AddWithValue("@pdcProd", txtPdcProdName.Text);
LabeledProductsDataTableAdapter.UpdateCommand.Parameters.AddWithValue("@lblProd", txtLabeledProd.Text);
LabeledProductsDataTableAdapter.UpdateCommand.Parameters.AddWithValue("@lblDesc", txtLabelDesc.Text);
LabeledProductsDataTableAdapter.UpdateCommand.Parameters.AddWithValue("@pwr", txtPower.Text);
LabeledProductsDataTableAdapter.UpdateCommand.Parameters.AddWithValue("@ul", txtULFileNumber.Text);
LabeledProductsDataTableAdapter.UpdateCommand.Parameters.AddWithValue("@pps", cbPrePrintedSerial.Checked);
LabeledProductsDataTableAdapter.UpdateCommand.Parameters.AddWithValue("@plid", LogoId);
LabeledProductsDataTableAdapter.UpdateCommand.Parameters.AddWithValue("@aid", AgencyId);
LabeledProductsDataTableAdapter.UpdateCommand.Parameters.AddWithValue("@wid", WireId);
LabeledProductsDataTableAdapter.UpdateCommand.Parameters.AddWithValue("@rfp", cbReleased.Checked);
//int rowsAffected = LabeledProductsDataTableAdapter.Update(LabeledProductsDataTable);
int rowsAffected = command.ExecuteNonQuery();
// The DataViewManager returned by the DefaultViewManager
// property allows you to create custom settings for each
// DataTable in the DataSet.
DataViewManager dsView = this.tILEDataSet.DefaultViewManager;
// remove the selectionChanged event handler during updates
// every update causes this handler to fire three times!!!
this.dataGridView1.SelectionChanged -= new System.EventHandler(this.dataGridView1_SelectionChanged);
dataGridView1.DataSource = typeof(TILEDataSet.vwTILEAdminDataTable);
this.vwTILEAdminBindingSource.DataSource = typeof(TILEDataSet.vwTILEAdminDataTable);
this.vwTILEAdminBindingSource.DataSource = this.tILEDataSet.vwTILEAdmin;
this.dataGridView1.DataSource = this.vwTILEAdminBindingSource;
vwTILEAdminBindingSource.ResetBindings(false); // false for data change, true for schema change
this.vwTILEAdminTableAdapter.Fill(this.tILEDataSet.vwTILEAdmin);
// we need to reget the row after the update to pass to preview
currentIndex = LastSelectedIndex;
DataGridViewRow drv = this.dataGridView1.Rows[currentIndex];
currentRow = ((DataRowView)(drv.DataBoundItem)).Row;
// update the preview files
UpdatePreviewFiles(currentRow);
// try this
dataGridView1.ClearSelection();
// this doesn't work
dataGridView1.Rows[currentIndex].Selected = true;
// reset the selection changed handler once the update is complete
this.dataGridView1.SelectionChanged += new System.EventHandler(this.dataGridView1_SelectionChanged);
}
}
}
After the update the form looks the same except the picture box is gone, and data from the first row shows up in the controls rather than the row that is highlighted.
All UpdatePreviewFiles does is replace the images with the updated ones. All ShowPreview does is set the image to the picturebox.Image. It all works until I do a save/update.
If there is anything else I can provide let me know, this is taking far too long to solve, I know there is a relatively simple explanation.
Thanks again.
Try doing:
this.picPreview.Image = Image.FromFile(imagePath);
Instead of messing around with FileStream
.
Also, you don't have to set this.picPreview.Image
to null
after disposing it.
When you call dispose, it releases all resources regardless to whether you have a pointer to it or not.
Setting null to something, or in other and more accurate words, losing any reference (pointer) to an object - will cause the GC (Garbage Collector) to release it's resources.
Using the Dispose method will allow the GC to release it even though you still have a reference to it. (Thanks Rowland Shaw), so simply re-setting it to Image.FromFile(imagePath)
will work just fine.
The reference to the previous image will be lost and the GC will dispose it when it feels like it (won't be long, I promise).
So to sum it up, I would suggest replacing the WHOLE piece of code with the single line in the beginning of this answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With