i created a winforms application. what it does is it queries a database and displays the data on a chart on the screen.
my question is, is it proper to use classes for this? i know the answer is probably yes, but i have no clue how to use a class for this,.
here is my code. please give me a few pieces of advice on how to turn this into a class if you think that is the right thing to do:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using System.Data.OleDb;
using System.Data.SqlClient;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private DataTable qResults = new DataTable();
private void Form1_Load(object sender, EventArgs e)
{
string qcvalues_query = "SELECT DISTINCT name FROM qvalues ORDER by name";
string analytes_query = "SELECT DISTINCT compound FROM qvalues ORDER by compound";
string instruments_query = "SELECT DISTINCT instrument FROM batchinfo WHERE instrument <> '' AND instrument is not Null ORDER by instrument";
dataGridView1.MultiSelect = false;
cbAnalytes.DisplayMember = "name";
cbAnalytes.DataSource = ConnectandReadList(qcvalues_query);
cbQCValues.DisplayMember = "compound";
cbQCValues.DataSource = ConnectandReadList(analytes_query);
cbInstruments.DisplayMember = "instrument";
cbInstruments.DataSource = ConnectandReadList(instruments_query);
}
private DataSet GetSeriesValues()
{
Series ser = this.chart1.Series["Series1"];
DataSet dataSet = new DataSet();
DataTable seriesTable = new DataTable(ser.Name);
seriesTable.Columns.Add(new DataColumn("No", typeof(int)));
seriesTable.Columns.Add(new DataColumn("X", typeof(string)));
seriesTable.Columns.Add(new DataColumn("Y", typeof(double)));
for (int count = 0; count < ser.Points.Count; count++)
{
DataPoint p = ser.Points[count];
seriesTable.Rows.Add(new object[] { count, p.XValue, p.YValues[0] });
}
dataSet.Tables.Add(seriesTable);
return dataSet;
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
// Call Hit Test Method
HitTestResult result = chart1.HitTest(e.X, e.Y);
// Reset Data Point Attributes
foreach (DataPoint point in chart1.Series[0].Points)
{
point.BackSecondaryColor = Color.Black;
point.BackHatchStyle = ChartHatchStyle.None;
point.BorderWidth = 1;
}
// If a Data Point or a Legend item is selected.
if
(result.ChartElementType == ChartElementType.DataPoint ||
result.ChartElementType == ChartElementType.LegendItem)
{
try
{
// Set cursor type
this.Cursor = Cursors.Hand;
// Find selected data point
DataPoint point = chart1.Series[0].Points[result.PointIndex];
// Set End Gradient Color to White
point.BackSecondaryColor = Color.White;
// Set selected hatch style
point.BackHatchStyle = ChartHatchStyle.Percent25;
// Increase border width
point.BorderWidth = 2;
}
catch { }
}
else
{
// Set default cursor
this.Cursor = Cursors.Default;
}
}
private void InitializeChart()
{
chart1.Series["Series1"].ChartType = SeriesChartType.Line;
chart1.Series["Series1"].MarkerStyle = MarkerStyle.Circle;
chart1.Series["Series1"].MarkerSize = 8;
// Set series members names for the X and Y values
chart1.Series["Series1"].XValueMember = "datapath";
chart1.Series["Series1"].YValueMembers = "finalconc";
chart1.DataBind();
// Calculate Mean
double mean = chart1.DataManipulator.Statistics.Mean("Series1");
// Calculate Median
double median = chart1.DataManipulator.Statistics.Median("Series1");
// Calculate Standard Deviation from the Variance
double variance = chart1.DataManipulator.Statistics.Variance("Series1", true);
double standardDeviation = Math.Sqrt(variance);
// Set Strip line item
chart1.ChartAreas[0].AxisY.StripLines[0].IntervalOffset = mean - Math.Sqrt(variance);
chart1.ChartAreas[0].AxisY.StripLines[0].StripWidth = 2.0 * Math.Sqrt(variance);
// Set Strip line item
chart1.ChartAreas[0].AxisY.StripLines[1].IntervalOffset = mean;
// Set Strip line item
chart1.ChartAreas[0].AxisY.StripLines[2].IntervalOffset = median;
DataPoint maxValuePoint = chart1.Series["Series1"].Points.FindMaxByValue();
DataPoint minValuePoint = chart1.Series["Series1"].Points.FindMinByValue();
chart1.ChartAreas[0].AxisY.Maximum = maxValuePoint.YValues.Max();
chart1.ChartAreas[0].AxisY.Minimum = minValuePoint.YValues.Min();
// Refresh Chart
chart1.Invalidate();
}
private DataTable ConnectandReadList(string query)
{
DataTable ds = new DataTable();
string connection_string = "Data Source=hermes;database=qcvalues; Integrated Security=SSPI;";
using (var myConnection = new SqlConnection(connection_string))
{
myConnection.Open();
var command = new SqlCommand(query, myConnection);
var adapter = new SqlDataAdapter(command);
adapter.Fill(ds);
}
return ds;
}
private void btnGenerateGraph_Click(object sender, EventArgs e)
{
string graph_query = #"SELECT top 1000 reporttime,
datapath,
finalconc,
instrument
FROM batchinfo
JOIN qvalues ON batchinfo.rowid = qvalues.rowid
WHERE compound = '" + cbQCValues.Text + "'" +
"AND name = '" + cbAnalytes.Text + "'" +
"AND batchinfo.instrument = '" + cbInstruments.Text + "'" +
"AND batchinfo.reporttime LIKE '10/%/2010%'";
qResults = ConnectandReadList(graph_query);
if (qResults.Rows.Count == 0)
{
MessageBox.Show("Your query did not return any results!");
return;
}
chart1.DataSource = qResults;
InitializeChart();
dataGridView1.Columns.Clear();
dataGridView1.DataBindings.Clear();
dataGridView1.DataSource = qResults;
}
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
// Call Hit Test Method
HitTestResult result = chart1.HitTest(e.X, e.Y);
if (result.ChartElementType == ChartElementType.DataPoint)
{
dataGridView1.Rows[result.PointIndex].Selected = true;
dataGridView1.FirstDisplayedScrollingRowIndex = result.PointIndex;
}
}
private void btnDelete_Click(object sender, EventArgs e)
{
Int32 selectedRowCount =
dataGridView1.Rows.GetRowCount(DataGridViewElementStates.Selected);
if (selectedRowCount == 1)
{
qResults.Rows.RemoveAt(dataGridView1.SelectedRows[0].Index);
InitializeChart();
}
}
}
}
How you organize your code depends on the scale and complexity of your application.
If you are writing a large application you probably want to use different layers to deal with the database, business logic and presentation.
For a small application like yours it is probably simpler to use data binding directly to a database query.
You are already using classes, but not the cleanest, most modular way. It's generally thought a bad idea to combine your data manipulation and business logic in a single UI class. This is why the MVC composite pattern was invented, to separate your data, the UI and biz logic into more modular pieces.
Here's another article, C# specific, to look at.
I think this is a subjective question... If it works already, why fix it?
But from another point of view, if this was part of a larger system, I would split it up not just into separate classes, but separate assemblies and namespaces.
You code is correct, for the task at hand. I would not worry about it right now, unless you are already a proficient programmer (know the syntax) and the goal is to implement good modular system design for a larger scale system.
I'm with slaks on this. Form1 is a class.
The only real change I would possibly suggest at this point would be to turn that into a composite control. That way you could drop that functionality onto different forms as necessary.
Check out the MS walkthrough and a smaller article here.
You might do that for no other reason than to learn something new.
Related
I have a DataGridView (DGV) that is NOT using data binding The data is in a SQLite DB
I have written a method that styles the DataGridView
I can populate the DGV with the id of the records in the DB and two string variables
For testing I have 4 TextBox's on the form when I click on the cells I would like to
retrieve the id to transfer that value to another form to use in SQL search
I have tried a number of methods to get data by clicking on the DGV all three are in the
posted code NO I DO NOT WANT to use data binding
SIDE NOTE I have this code working in a VB.Net application no issues
I am new to C# so the code conversion may be the real issue
SO the question is Using C# How to click on a DGV and retrieve the selected value?
public partial class frmSelect : Form
{
string gv_parentInt = "";
string gv_firstName = "";
string gv_lastName = "";
public frmSelect()
{
InitializeComponent();
}
private void frmSelect_Load(object sender, EventArgs e)
{
StyleDGV();
readFriendsData();
}
// Thried these two methods below NO RESULTS
/* private void dgvPerson_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.RowIndex >= 0)
{
DataGridViewRow row = dgvPerson.Rows[e.RowIndex];
tbID.Text = row.Cells[0].Value.ToString();
tbFName.Text = row.Cells[1].Value.ToString();
tbLName.Text = row.Cells[2].Value.ToString();
}
}*/
/*public void dgvPerson_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (dgvPerson.Rows[e.RowIndex].Cells[e.ColumnIndex].Value != null)
{
tbMessage.Text = dgvPerson.SelectedCells[0].Value.ToString();
}
}*/
public void dgvPerson_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex == -1 || e.ColumnIndex == -1)
{
tbMessage.Text = "Col " + e.ColumnIndex + " ROW " + e.RowIndex;// for testing
tbMessage.Text = "No Clicking Here";
//return;
}
string strFName;
string strLName;
DataGridViewRow row = dgvPerson.Rows[e.RowIndex];
strFName = dgvPerson.CurrentRow.Cells[0].Value.ToString();
if (strFName != " ")
{
if (strFName == " ")
return;
int intId = System.Convert.ToInt32(row.Cells[0].Value);
gv_parentInt = intId.ToString();
tbID.Text = gv_parentInt;
strFName = row.Cells[1].Value.ToString();
gv_firstName = strFName.Trim();
tbFName.Text = gv_firstName;
strLName = row.Cells[2].Value.ToString();
gv_lastName = strLName.Trim();
tbLName.Text = gv_lastName;
}
tbMessage.Text = "No Data Here";
}
private void readFriendsData()
{
using (SQLiteConnection conn = new SQLiteConnection($"Data Source = '{"Contacts.db"}';Version=3;"))
{
conn.Open();
// The $ sign and '{String or Integer}' how to add variable to SQL Select Statement'gv_parentInt
// Must incorparate BOTH for SELECT to work
// =================================================================================
using (SQLiteCommand cmd = new SQLiteCommand($"SELECT * FROM FriendsData", conn))
{
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
gv_parentInt = rdr["FID"].ToString().Trim();
gv_firstName = rdr["fxFirstName"].ToString().Trim();
gv_lastName = rdr["fxLastName"].ToString().Trim();
dgvPerson.Rows.Add(gv_parentInt, gv_firstName, gv_lastName);
}
rdr.Close();
}
}
conn.Close();
}
//frmPrintLabel.LoadLabel();
}
public void StyleDGV()
{
this.Controls.Add(dgvPerson);
// Set Design of the DataGridView
dgvPerson.DefaultCellStyle.Font = new Font("Bold Tahoma", 11);
dgvPerson.ColumnCount = 3;
dgvPerson.Columns[0].Width = 60;
dgvPerson.Columns[1].Width = 138;
dgvPerson.Columns[2].Width = 138;
// To Set Col Header Size Mode = Enabled
// To Set Col Header Default Cell Styles DO in Properties
dgvPerson.DefaultCellStyle.BackColor = Color.LightBlue;
dgvPerson.AlternatingRowsDefaultCellStyle.BackColor = Color.LightGray;
dgvPerson.ColumnHeadersHeight = 34;
// DGV Header Names
dgvPerson.Columns[0].Name = " ID";
dgvPerson.Columns[1].Name = "First Name";
dgvPerson.Columns[2].Name = "Last Name";
dgvPerson.Columns[0].HeaderCell.Style.Font = new Font("Bold Tahoma", 11);
dgvPerson.Columns[1].HeaderCell.Style.Font = new Font("Bold Tahoma", 11);
dgvPerson.Columns[2].HeaderCell.Style.Font = new Font("Bold Tahoma", 11);
dgvPerson.Columns[0].HeaderCell.Style.ForeColor = Color.Blue;
dgvPerson.Columns[1].HeaderCell.Style.ForeColor = Color.Blue;
dgvPerson.Columns[2].HeaderCell.Style.ForeColor = Color.Blue;
dgvPerson.Columns[0].SortMode = DataGridViewColumnSortMode.NotSortable;
dgvPerson.Columns[1].SortMode = DataGridViewColumnSortMode.NotSortable;
dgvPerson.Columns[2].SortMode = DataGridViewColumnSortMode.NotSortable;
}
private void btnReturn_Click(object sender, EventArgs e)
{
Close();
frmStart fST = new frmStart();
fST.Show();
}
}
}
First let me say that using the TOOL from a VB.Net project was a BIG 4 hour mistake
Here is the code I used to capture the values in the DataGridView
private void dgvPerson_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
int ID = 0;// Declare this top level
ID = Convert.ToInt32(dgvPerson.Rows[e.RowIndex].Cells[0].Value.ToString());
tbID.Text = ID.ToString();
tbFName.Text = dgvPerson.Rows[e.RowIndex].Cells[1].Value.ToString();
tbLName.Text = dgvPerson.Rows[e.RowIndex].Cells[2].Value.ToString();
}
Here is what was WRONG Visual Studio 2019 has two DataGridView Tools
One under All Windows Forms and one under Data (see attached screen shot)
I used the one under All Windows Form in the ToolBox hence my code FAILED
Use the DGV under Data in the ToolBox if your are working with C# and SQLite
I am having an issue with my program. my program will let the user select a dropdown value and after they select the dropdown value the datagridview should load with the data from a table, and then the progress bar should start the percentage 1 to 100%. Now everything in my program works the datagridview loads correctly and everything else in the program. the progress bar also works and loads but the issue comes when the user selects the dropdow combobox the progress bar takes like 15 to 20 seconds to start. I would like it to right away.
Can you see my code and see what could be the issue to why the progress bar is not starting right away?
if you need more information please let me know.
namespace DatagridViewProgressBar
{
public partial class Form1 : Form
{
//datagridview, bindingsource, data_apapter global objects variables
private DataGridView dataGridView = new DataGridView();
private BindingSource bindingSource = new BindingSource();
private SqlDataAdapter dataAdapter = new SqlDataAdapter();
DataTable dt = new DataTable();
//class objects
Databases lemars = new Databases();
Databases schuyler = new Databases();
Databases detroitlakeskc = new Databases();
public Form1()
{
InitializeComponent();
// To report progress from the background worker we set this property
dbWorker = new BackgroundWorker();
dbWorker.DoWork += new DoWorkEventHandler(dbWorker_DoWork);
dbWorker.ProgressChanged += new ProgressChangedEventHandler(dbWorker_ProgressChanged);
dbWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(dbWorker_RunWorkerCompleted);
dbWorker.WorkerReportsProgress = true;
dbWorker.WorkerSupportsCancellation = true;
}
private void btn_Exit_Click(object sender, EventArgs e)
{
this.Close();
}
private void comboBox_Database_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox_Database.SelectedItem.ToString() == "LeMars21St")
{
if (dbWorker.IsBusy != true)
{
dbWorker.RunWorkerAsync();
}
}
}
private void GetTableToDataGridView()
{
//prgBar_DataGridViewLoading
DatabaseColumns Obj = new DatabaseColumns();
String SqlcmdString = #"SELECT invoice, shipment, Project, invoiceDateTB, CreatedDate, typeName, exportedDate, statusName, total, import_status, Time_Completed, ERROR_DESCRIPTION FROM dbo.AllInvoicesInReadyStatus";
SqlDataReader reader;
int progress;
using (SqlConnection conn = new SqlConnection(lemars._LeMarsConnectionString))
{
reader = null;
SqlCommand Sqlcmd = new SqlCommand(SqlcmdString, conn);
conn.Open();
reader = Sqlcmd.ExecuteReader();
if (reader.HasRows)
{
try
{
dt.Load(reader);
for (int i = 0; i < dt.Rows.Count; i++)
{
Obj.Invoice = dt.Rows[i]["invoice"].ToString();
Obj.Shipment = dt.Rows[i]["shipment"].ToString();
Obj.Project = dt.Rows[i]["Project"].ToString();
Obj.InvoiceDateTB = Convert.ToDateTime(dt.Rows[i]["invoiceDateTB"]);
Obj.CreatedDate = Convert.ToDateTime(dt.Rows[i]["CreatedDate"]);
Obj.TypeName = dt.Rows[i]["typeName"].ToString();
Obj.ExportedDate = Convert.ToDateTime(dt.Rows[i]["exportedDate"]);
Obj.StatusName = dt.Rows[i]["statusName"].ToString();
Obj.Total = Convert.ToDecimal(dt.Rows[i]["total"]);
Obj.ImportStatus = dt.Rows[i]["import_status"].ToString();
if (!Convert.IsDBNull(dt.Rows[i]["Time_Completed"]))
{
Obj.TimeCompleted = Convert.ToDateTime(dt.Rows[i]["Time_Completed"]);
}
Obj.ErrorDescription = dt.Rows[i]["ERROR_DESCRIPTION"].ToString();
progress = i * 100 / dt.Rows.Count;
dbWorker.ReportProgress(progress);
Thread.Sleep(500);
}
}
finally
{
conn.Close();
}
}
}
}
private void dbWorker_DoWork(object sender, DoWorkEventArgs e)
{
GetTableToDataGridView();
dbWorker.ReportProgress(100);
}
private void dbWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar_GetTasks.Value = e.ProgressPercentage;
// eg: Set your label text to the current value of the progress bar
lbl_PercentageCount.Text = (progressBar_GetTasks.Value.ToString() + "%");
}
private void dbWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
dataGridView_ShowAllData.DataSource = dt;
if (e.Cancelled)
{
MessageBox.Show("Process Cancelled.");
}
else if (e.Error != null)
{
MessageBox.Show("Error occurred: " + e.Error.Message);
}
else
{
MessageBox.Show("Successful Completion.");
}
//progressBar_GetTasks.Value = 0;
}
private void btn_CancelOperation_Click(object sender, EventArgs e)
{
if (dbWorker.IsBusy)
{
dbWorker.CancelAsync();
}
}
}
}
Doing dt.Load(reader); waits for the data to fully load before continuing, Get rid of that line and replace it with a while(reader.Read()) loop.
private void GetTableToDataGridView()
{
//prgBar_DataGridViewLoading
DatabaseColumns Obj = new DatabaseColumns();
String SqlcmdString = #"SELECT invoice, shipment, Project, invoiceDateTB, CreatedDate, typeName, exportedDate, statusName, total, import_status, Time_Completed, ERROR_DESCRIPTION FROM dbo.AllInvoicesInReadyStatus";
String CountcmdString = #"SELECT count(*) FROM dbo.AllInvoicesInReadyStatus";
SqlDataReader reader;
int progress;
int total;
using (SqlConnection conn = new SqlConnection(lemars._LeMarsConnectionString))
{
reader = null;
SqlCommand Sqlcmd = new SqlCommand(CountcmdString , conn);
conn.Open();
total = (int)Sqlcmd.ExecuteScalar(); //Get the total count.
Sqlcmd.CommandText = SqlcmdString;
using(reader = Sqlcmd.ExecuteReader()) //this should be in a using statement
{
while(reader.Read())
{
object[] row = new object[reader.VisibleFieldCount];
reader.GetValues(row);
LoadSingleRowInToTable(dt, row); //I leave this to you to write.
//You can just read directly from the reader.
Obj.Invoice = reader["invoice"].ToString();
Obj.Shipment = reader["shipment"].ToString();
Obj.Project = reader["Project"].ToString();
Obj.InvoiceDateTB = Convert.ToDateTime(reader["invoiceDateTB"]);
Obj.CreatedDate = Convert.ToDateTime(reader["CreatedDate"]);
Obj.TypeName = reader["typeName"].ToString();
Obj.ExportedDate = Convert.ToDateTime(reader["exportedDate"]);
Obj.StatusName = reader["statusName"].ToString();
Obj.Total = Convert.ToDecimal(reader["total"]);
Obj.ImportStatus = reader["import_status"].ToString();
if (!Convert.IsDBNull(reader["Time_Completed"]))
{
Obj.TimeCompleted = Convert.ToDateTime(reader["Time_Completed"]);
}
Obj.ErrorDescription = reader["ERROR_DESCRIPTION"].ToString();
//Only call report progress when the progress value changes.
var newProgress = i * 100 / total;
if(progress != newProgress)
{
progress = newProgress;
dbWorker.ReportProgress(progress);
}
//Thread.Sleep(500);
}
}
}
}
UPDATE: Here is a example based on Steve's deleted answer that shows a better solution without using a DataTable.
private void dbWorker_DoWork(object sender, DoWorkEventArgs e)
{
List<DatabaseColumns> data = GetTableToList();
if (data == null) //data will be null if we canceled.
{
e.Cancel = true;
}
else
{
e.Result = data;
}
dbWorker.ReportProgress(100);
}
private void dbWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Process Cancelled.");
}
else if (e.Error != null)
{
MessageBox.Show("Error occurred: " + e.Error.Message);
}
else
{
dataGridView_ShowAllData.DataSource = e.Result; //use the result from thebackground worker, only use if not canceled or errored.
MessageBox.Show("Successful Completion.");
}
//progressBar_GetTasks.Value = 0;
}
private List<DatabaseColumns> GetTableToList()
{
List<DatabaseColumns> data = new List<DatabaseColumns>();
//prgBar_DataGridViewLoading
String SqlcmdString = #"SELECT invoice, shipment, Project, invoiceDateTB, CreatedDate, typeName, exportedDate, statusName, total, import_status, Time_Completed, ERROR_DESCRIPTION FROM dbo.AllInvoicesInReadyStatus";
String CountcmdString = #"SELECT count(*) FROM dbo.AllInvoicesInReadyStatus";
using (SqlConnection conn = new SqlConnection(lemars._LeMarsConnectionString))
{
SqlCommand Sqlcmd = new SqlCommand(CountcmdString, conn);
conn.Open();
var total = (int)Sqlcmd.ExecuteScalar();
Sqlcmd.CommandText = SqlcmdString;
int i = 0;
int progress = 0;
using (SqlDataReader reader = Sqlcmd.ExecuteReader()) //this should be in a using statement
{
while (reader.Read())
{
if (dbWorker.CancellationPending)
{
//Exit early if operation was canceled.
return null;
}
DatabaseColumns Obj = new DatabaseColumns();
//You can just read directly from the reader.
Obj.Invoice = reader["invoice"].ToString();
Obj.Shipment = reader["shipment"].ToString();
Obj.Project = reader["Project"].ToString();
Obj.InvoiceDateTB = Convert.ToDateTime(reader["invoiceDateTB"]);
Obj.CreatedDate = Convert.ToDateTime(reader["CreatedDate"]);
Obj.TypeName = reader["typeName"].ToString();
Obj.ExportedDate = Convert.ToDateTime(reader["exportedDate"]);
Obj.StatusName = reader["statusName"].ToString();
Obj.Total = Convert.ToDecimal(reader["total"]);
Obj.ImportStatus = reader["import_status"].ToString();
if (!Convert.IsDBNull(reader["Time_Completed"]))
{
Obj.TimeCompleted = Convert.ToDateTime(reader["Time_Completed"]);
}
Obj.ErrorDescription = reader["ERROR_DESCRIPTION"].ToString();
//Add the object to the list.
data.Add(Obj);
//Only call report progress when the progress value changes.
var newProgress = i * 100 / total;
if (progress != newProgress)
{
progress = newProgress;
dbWorker.ReportProgress(progress);
}
i++;
}
}
}
return data;
}
Something I had when doing my first backgroundWorker, was the GUI locking up. I made the mistake of trying to hand out every result (primnumbers in a range from 2 to selected bound) as it was found via report progress. If I had a long enough operation (it did not happen if the operation was short) I ended up overloading and locking up the GUI with write operations, making it appear I never actually added multithreading.
Now I noticed this line:
progress = i * 100 / dt.Rows.Count;
You run the progress code (and update the bar) every loop itteration, even if the percentage did not actually change. If you process 1 million itterations, that is 10000 redraws without the value actually changing. This might slow down later as hits become rarer or the GC starts interferring with optimal performance of the task so the GUI thread has "time to catch up".
You should check if the value actually changed, before writing it to the GUI. Latest in the ProgressReporting Event. But it might be possible to do something back in the loop itself.
I made some example code to showcase what I call the "GUI write overhead" problem:
using System;
using System.Windows.Forms;
namespace UIWriteOverhead
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
int[] getNumbers(int upperLimit)
{
int[] ReturnValue = new int[upperLimit];
for (int i = 0; i < ReturnValue.Length; i++)
ReturnValue[i] = i;
return ReturnValue;
}
void printWithBuffer(int[] Values)
{
textBox1.Text = "";
string buffer = "";
foreach (int Number in Values)
buffer += Number.ToString() + Environment.NewLine;
textBox1.Text = buffer;
}
void printDirectly(int[] Values){
textBox1.Text = "";
foreach (int Number in Values)
textBox1.Text += Number.ToString() + Environment.NewLine;
}
private void btnPrintBuffer_Click(object sender, EventArgs e)
{
MessageBox.Show("Generating Numbers");
int[] temp = getNumbers(10000);
MessageBox.Show("Printing with buffer");
printWithBuffer(temp);
MessageBox.Show("Printing done");
}
private void btnPrintDirect_Click(object sender, EventArgs e)
{
MessageBox.Show("Generating Numbers");
int[] temp = getNumbers(1000);
MessageBox.Show("Printing directly");
printDirectly(temp);
MessageBox.Show("Printing done");
}
}
}
This question already has answers here:
C# - Winforms - Global Variables
(8 answers)
Closed 9 years ago.
I have been trying, without success, to share a variable between multiple forms. I am very new to c# and so have been failing miserably despite reading a couple of things about it.. Below is the programs code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data.Common;
using System.Data.OleDb;
namespace login
{
public partial class LoginScreen : Form
{
public LoginScreen()
{
InitializeComponent();
}
// Variables
int count = 0;
public static System.Data.OleDb.OleDbConnection con =
new System.Data.OleDb.OleDbConnection();//database connection
string dbProvider;
string dbSource;
OleDbDataAdapter da; // create database adapter 'da'
// CREATE DATASET VARIABLE ds1 TO HOLD THE DATABASE
public static DataSet ds1 = new DataSet();
string accountNo;
string sql;
string password;
int rownum = 0;
bool valid = false;
private void btnLogin_Click(object sender, EventArgs e)
{
accountNo = txtBoxAccntNo.Text;
valid = validate(); //uses validate() method to check validity
if (valid == true && accountNo == "11111111")
{
ManagerScreen Manager = new ManagerScreen();
this.Hide();
Manager.Show();
}
else if (valid == true)
{
s customer = new s();
this.Hide();
customer.Show();
}
else
{
if (count == 2)
{
this.Close();
}
count += 1;
txtBoxAccntNo.Clear();
txtBoxPinNo.Clear();
}
}
private void txtBoxAccntNo_TextChanged(object sender, EventArgs e)
{
}
private void LoginScreen_Load(object sender, EventArgs e)
{
// open database connection and load contents
// database connection
dbProvider = "PROVIDER=Microsoft.ACE.OLEDB.12.0;"; // this is the database provider
dbSource = "Data Source = 'C:\\Bank.accdb'"; // navigation path
con.ConnectionString = dbProvider + dbSource;
}
private void btnExit_Click(object sender, EventArgs e)
{
// If button exit selected hide this form and open the welcome screen
WelcomeForm Welcome = new WelcomeForm();
this.Hide();
Welcome.Show();
}
// IsValid method checks that pass and login are valid
private bool validate()
{
ds1 = new DataSet();
con.Open();
// Validate Account number
sql = "SELECT * FROM tblCustomers WHERE ((tblCustomers.AccountNo) = '" + txtBoxAccntNo.Text + "')";
da = new OleDbDataAdapter(sql, con);
rownum = da.Fill(ds1, "tblCustomers");
con.Close();
if (rownum != 1)
{
MessageBox.Show("Not a valid Account number! - Try Again ");
return false;
}
else
{
// validate the pin
password = ds1.Tables["tblCustomers"].Rows[0][4].ToString();
if (password == txtBoxPinNo.Text)
{
MessageBox.Show("valid");
return true;
}
else
{
MessageBox.Show("Not a valid password - please try again ");
return false;
}
}
}
}
}
I want to share the variable accountNo with all other forms. Please advise, as I really need to get on with this. Thank you for any help.
You can make that accountNo property as static or either you can have some getter method to access that too.
If you set accountNo as static you can access it by just calling
ClassName.PropertyName
in your case
LoginScreen.accountNo will be the account number property.
Simple code sample
public partial class LoginScreen : Form
{
public LoginScreen()
{
InitializeComponent();
}
public static string accountNo;
}
public class AnotherClass
{
string accountNo = LoginScreen.accountNo;
}
The right way to go about this is to use the form to retrieve the information and then store it somewhere else to be accessed as you need it. Don't access it directly in the form from elsewhere - this will require you to keep the login form in scope for the whole application lifecycle. This probably isn't what you want.
In practice, this means creating something like a Global static class that everything has access to:
public static class Globals {
public static string AccountNumber {
get;
set;
}
}
From in your login form, after validating the login as correct, you would simply do:
Globals.AccountNumber = txtBoxAcctNo.Text;
Then, anywhere else you need the AccountNumber, you can access it as Globals.AccountNumber.
I can recommend one of three ways to achieve what you want:
Make accountNo a public static variable. Then, other forms can access it by LoginScreen.accountNo (it's better to have a property to control visibility). This is a good approach if you might have many active instances of LoginScreen and they all might update accountNo and you want any form which accesses this field to get the latest value.
Implement a singleton pattern for the entire form and have accountNo in it as a public variable. This is a good approach if you will only have one instance of the firm active at any time.
Have accountNo be static member in another class and have LoginScreen access it by UtilClass.accountNo. This is a good approach if other forms/classes might want to update the field and/or it's a field which shouldn't be associated with this form.
I have following in Windows Forms .NET 3.5
It works fine for csv with records less than 10,000 but is slower for records above 30,000.
Input csv file can can any records between 1 - 1,00,000 records
Code currently used :
/// <summary>
/// This will import file to the collection object
/// </summary>
private bool ImportFile()
{
try
{
String fName;
String textLine = string.Empty;
String[] splitLine;
// clear the grid view
accountsDataGridView.Rows.Clear();
fName = openFileDialog1.FileName;
if (System.IO.File.Exists(fName))
{
System.IO.StreamReader objReader = new System.IO.StreamReader(fName);
do
{
textLine = objReader.ReadLine();
if (textLine != "")
{
splitLine = textLine.Split(',');
if (splitLine[0] != "" || splitLine[1] != "")
{
accountsDataGridView.Rows.Add(splitLine);
}
}
} while (objReader.Peek() != -1);
}
return true;
}
catch (Exception ex)
{
if (ex.Message.Contains("The process cannot access the file"))
{
MessageBox.Show("The file you are importing is open.", "Import Account", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
else
{
MessageBox.Show(ex.Message);
}
return false;
}
}
Sample Input file :
18906,Y
18908,Y
18909,Y
18910,Y
18912,N
18913,N
Need some advice on optimizing this code for fast reads & view in grid.
List<string[]> rows = File.ReadAllLines("Path").Select(x => x.Split(',')).ToList();
DataTable dt = new DataTable();
dt.Columns.Add("1");
dt.Columns.Add("2");
rows.ForEach(x => {
dt.Rows.Add(x);
});
dgv.DataSource = dt;
Try that, I suspected that you have some form of column names in the datagrid for now I just made them 1 and 2.
To filter as per your original code use:
List<string[]> rows = File.ReadAllines("Path").Select(x => x.Split(',')).Where(x => x[0] != "" && x[1] != "").ToList();
To get your columns from the DataGridView
dt.Columns.AddRange(dgv.Columns.Cast<DataGridViewColumn>().Select(x => new DataColumn(x.Name)).ToArray());
There isn't much to optimize in regards to speed, but following is much more readable. If it is too slow, it probably isn't the method reading the file, but your WinForm that needs to display >30k records.
accountsDataGridView.Rows.Clear();
using (FileStream file = new FileStream(openFileDialog1.FileName, FileMode.Open, FileAccess.Read, FileShare.Read, 4096))
using (StreamReader reader = new StreamReader(file))
{
while (!reader.EndOfStream)
{
var fields = reader.ReadLine().Split(',');
if (fields.Length == 2 && (fields[0] != "" || fields[1] != ""))
{
accountsDataGridView.Rows.Add(fields);
}
}
}
You can try to use SuspendLayout() and ResumeLayout() Methods.
From MSDN Documentation
"The SuspendLayout and ResumeLayout methods are used in tandem to suppress multiple Layout events while you adjust multiple attributes of the control. For example, you would typically call the SuspendLayout method, then set the Size, Location, Anchor, or Dock properties of the control, and then call the ResumeLayout method to enable the changes to take effect."
accountsDataGridView.SuspendLayout();
accountsDataGridView.Rows.Clear();
// .....
// in the end after you finished populating your grid call
accountsDataGridView.ResumeLayout();
Instead of putting the data directly into the grid you should take a look at the VirtualMode of the DataGridView.
In your code you're doing two things at one time (read the file, fill the grid), which leads to your freezed gui. Instead you should put the grid into the virtual mode and read the file within a BackgroundWorker into a list which holds the data for the grid. The background worker can after each line read update the virtual size of the grid, which allows to already see the data while the grid is loading. By using this approach you'll getting a smooth working grid.
Below you'll find an example which just needs to be filled into a form that uses a DataGridView with two text columns, a BackgroundWorker and a Button:
public partial class FormDemo : Form
{
private List<Element> _Elements;
public FormDemo()
{
InitializeComponent();
_Elements = new List<Element>();
dataGridView.AllowUserToAddRows = false;
dataGridView.AllowUserToDeleteRows = false;
dataGridView.ReadOnly = true;
dataGridView.VirtualMode = true;
dataGridView.CellValueNeeded += OnDataGridViewCellValueNeeded;
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += OnBackgroundWorkerDoWork;
backgroundWorker.ProgressChanged += OnBackgroundWorkerProgressChanged;
backgroundWorker.RunWorkerCompleted += OnBackgroundWorkerRunWorkerCompleted;
}
private void OnBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
var filename = (string)e.Argument;
using (var reader = new StreamReader(filename))
{
string line = null;
while ((line = reader.ReadLine()) != null)
{
var parts = line.Split(',');
if (parts.Length >= 2)
{
var element = new Element() { Number = parts[0], Available = parts[1] };
_Elements.Add(element);
}
if (_Elements.Count % 100 == 0)
{
backgroundWorker.ReportProgress(0);
}
}
}
}
private void OnBackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
dataGridView.RowCount = _Elements.Count;
}
private void OnBackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
dataGridView.RowCount = _Elements.Count;
button.Enabled = true;
}
private void OnButtonLoadClick(object sender, System.EventArgs e)
{
if (!backgroundWorker.IsBusy
&& DialogResult.OK == openFileDialog.ShowDialog())
{
button.Enabled = false;
backgroundWorker.RunWorkerAsync(openFileDialog.FileName);
}
}
private void OnDataGridViewCellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
var element = _Elements[e.RowIndex];
switch (e.ColumnIndex)
{
case 0:
e.Value = element.Number;
break;
case 1:
e.Value = element.Available;
break;
}
}
private class Element
{
public string Available { get; set; }
public string Number { get; set; }
}
}
I'm have a Windows Form with 2 datetimepicker controls: one for date and a separate datetimepicker control for time. Both of these controls are bound to the same column in a database and the controls have different property names (i.e., dateEdit and timeEdit) and different formats (i.e., Long and Time).
Here are my problems/questions:
The timeEdit picker ignores whatever seconds are in the database entry and sets the time seconds to "00", as in "2:34:00", even though the database entry (trimmed to time for this illustration) is "14:34:31.891123 -04:00". How can I get the seconds to correctly display?
Whenever I edit the seconds in the timeEdit picker from "00" to (for example) "15", as in "2:34:15", the picker resets the seconds to "00" before passing the value to the next function. How do I pass the correct seconds value?
I'd like to edit the milliseconds on the time. Is it best for me to bind the trimmed milliseconds (using DATEPART) to a text box? Will I need to convert or cast the milliseconds to a char or string in order to correctly display them in a text box?
Thanks for any help!
Code to trigger the edit form:
private void timeDataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
try
{
if (e.ColumnIndex == 5)
{
EditTime editForm = new EditTime((Guid)timeDataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value);
editForm.StartPosition = FormStartPosition.CenterScreen;
editForm.ShowDialog();
editForm.Close();
}
}
catch (Exception ex)
{
string msg = "Error: ";
msg += ex.Message;
throw new Exception(msg);
}
}
Code for the form:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace StatusManager
{
public partial class EditTime : Form
{
private Guid calendarId;
public EditTime()
{
InitializeComponent();
}
public EditTime(Guid Id)
{
InitializeComponent();
calendarId = Id;
}
public string GetConnectionString()
{
var connString = ConfigurationManager.ConnectionStrings["StatusManager.Properties.Settings.StatusConnectionString"].ConnectionString;
return connString;
}
private void UpdateCalendarItem(string dateEdit, string timeEdit, string note)
{
var conn = new SqlConnection(GetConnectionString());
const string UpdateStatusSql = #"UPDATE dbo.statuses SET
calendarTime = #timeOffset
notes = #note
WHERE PK_calendarUID = #PK_calendarUID";
try
{
SqlCommand cmd = new SqlCommand(UpdateSql, conn);
var param = new SqlParameter[3];
param[0] = new SqlParameter("#PK_calendarUID", calendarId);
//Convert date(s) to correct format
string dateTimeCombined = dateEdit + " " timeEdit;
DateTime timeConverted = Convert.ToDateTime(dateTimeCombined);
DateTimeOffset timeOffset = new DateTimeOffset(timeConverted);
param[1] = new SqlParameter("#timeOffset", timeOffset);
param[2] = new SqlParameter("#note", note);
foreach (SqlParameter t in param)
{
cmd.Parameters.Add(t);
}
conn.Open();
cmd.CommandType = CommandType.Text;
cmd.ExecuteNonQuery();
}
catch (SqlException ex)
{
string msg = "Error updating 'calendarItems': ";
msg += ex.Message;
throw new Exception(msg);
}
finally
{
conn.Close();
}
}
private void editTimeButton_Click(object sender, EventArgs e)
{
UpdateCalendarItem(dateEdit.Text, timeEdit.Text, notes.Text);
this.Close();
}
private void EditTime_Load(object sender, EventArgs e)
{
this.locationsTableAdapter.Fill(this.locationsDataSet.locations);
this.calendarTableAdapter.FillById(this.calendarDataSet.calendarItems, calendarId);
}
}
}
Code for instantiating the datetimepicker:
this.timeEdit.CustomFormat = "";
this.timeEdit.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.calendarBindingSource, "calendarTime", true));
this.timeEdit.Format = System.Windows.Forms.DateTimePickerFormat.Time;
this.timeEdit.Location = new System.Drawing.Point(385, 30);
this.timeEdit.Name = "timeEdit";
this.timeEdit.ShowUpDown = true;
this.timeEdit.Size = new System.Drawing.Size(89, 20);
this.timeEdit.TabIndex = 2;
You need to use DateTimePicker.CustomFormat Property
s The one- or two-digit seconds.
ss The two-digit seconds. Single digit values are preceded by a 0.
You can't use DateTimePicker for milliseconds.
Problem solved but I'm not exactly sure how. Here's what I did:
In calendarDataSet, I updated both queries (Fill,GetData and FillById,GetDataBy (#ID)) to select calendarTime as CONVERT(VARCHAR(12), calendarTime, 114) AS calHoursMinsSec
In essence, I created created a new column with the hours, minutes, seconds, and milliseconds
On the form, I added a textbox and bound the textbox to calHoursMinsSec
Note: My previous attempts to convert the datetime to a varchar to were unsuccessful no doubt due to operator error.
Once I saved the form, the binding seemed to stick and I was able to pass the relevant variables to the update function
Thanks for everyone's input! I appreciate the guidance and suggestions!