dynamic autocomplete textbox on textchange event in winform with database - c#

how can achieve dynamic auto complete textbox in c# winform?
for example ,in my application have a textbox named txtbox_mobileno.
i wanna bind mobile number(from database table named customer) with txtbox_mobileno.
the table customer have more than 100,000 records .client want suggestion list while typing mobile number in application .
so i tried to implement following code on textchanged event
if (txtbox_mobileno.TextLength >3)
{
cmd.CommandText = "SELECT TOP 20 MobileNo FROM Customer WHERE MobileNo LIKE'" + txtbox_mobileno.Text+"%'";
SqlDataReader dr;
dr = RunSqlReturnDR(cmd);//return value
if (dr.HasRows)
{
while (dr.Read())
{
C.Add(dr[0].ToString()); //AutoCompleteStringCollection C = new AutoCompleteStringCollection();
}
}
dr.Close();
txtbox_mobileno.AutoCompleteMode = AutoCompleteMode.Suggest;
txtbox_mobileno.AutoCompleteSource = AutoCompleteSource.CustomSource;
txtbox_mobileno.AutoCompleteCustomSource = C;
}
i want to show the suggestion list only if text box have more than 3 letters
when tried to bind on load event, it took around 5 minutes to bind data with textbox suggestion list ,
Sql server query execution time was only 3 seconds.
is there any fastest way to overcome this issue?

I am not sure if you are able to grab all mobile numbers at the beginning of your app or at least when your data entry form loads up, but if so, it may be easier to load all of the numbers into buckets with the first three characters being the key for the list of numbers. Even if you had 1,000,000 numbers, you are only talking around 30 MB of data (assuming I did my math correctly), so you shouldn't have any issues in that regard. You could do something like this to build your buckets. As an aside, I also have no idea how many numbers you get that have the same first three digits, so I may be building very large autocomplete lists in this code which would be another problem. Your code only pulled back the top 20, you could modify this code to only keep the first 20 or something like that.
var buckets = new Dictionary<string, List<string>>();
cmd.CommandText = "SELECT MobileNo FROM Customer";
SqlDataReader dr = RunSqlReturnDR(cmd);
if (dr.HasRows)
{
while (dr.Read())
{
var number = dr[0].ToString();
var key = number.Substring(0, 3);
List<string> numbers = null;
if(!buckets.TryGetValue(key, out numbers))
{
numbers = new List<string>();
}
numbers.Add(number);
}
}
dr.Close();
Then in your event handler you just need to do something like this:
if (txtbox_mobileno.Text.Length == 3)
{
List<string> numbers;
if (_buckets.TryGetValue(txtbox_mobileno.Text, out numbers)
{
var ac = new AutoCompleteStringCollection();
ac.AddRange(numbers.ToArray());
txtbox_mobileno.AutoCompleteMode = AutoCompleteMode.Suggest;
txtbox_mobileno.AutoCompleteSource = AutoCompleteSource.CustomSource;
txtbox_mobileno.AutoCompleteCustomSource = ac;
}
}
Other ways you could potentially enhance this would be to add an order by in the query where you get the numbers, then you should be able to walk the values and just create a new list when you hit a change in the first three digits. That would make it easier to build a dictionary of arrays rather than a dictionary of lists, which would then help in your autocomplete box. I also haven't written a lot of DB code in a while, so I just kept the code you posted for getting the data out of the database. It is possible that there may be better ways to read the data from the database.

Try use a timer to fire query, and a Thread to execute, see:
private void textBox1_TextChanged(object sender, EventArgs e)
{
timer1.Enabled = false;
timer1.Enabled = true;
}
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Enabled = false;
string filter = txtbox_mobileno.Text;
Thread t = new Thread(() => {
cmd.CommandText = "SELECT TOP 20 MobileNo FROM Customer WHERE MobileNo LIKE '" + filter + "%'";
SqlDataReader dr;
dr = RunSqlReturnDR(cmd);//return value
if (dr.HasRows)
{
while (dr.Read())
{
C.Add(dr[0].ToString()); //AutoCompleteStringCollection C = new AutoCompleteStringCollection();
}
}
dr.Close();
txtbox_mobileno.Invoke((MethodInvoker)delegate {
txtbox_mobileno.AutoCompleteMode = AutoCompleteMode.Suggest;
txtbox_mobileno.AutoCompleteSource = AutoCompleteSource.CustomSource;
txtbox_mobileno.AutoCompleteCustomSource = C;
});
});
t.Start();
}

Related

how to make the datagridview load faster with Multiple Row query - c# SQL

The code:
private void suppPopulate()
{
byCode.Text = "Supplier Code";
byDesc.Text = "Supplier";
DGViewListItems.Columns.Add("custcode", "Customer Code");
DGViewListItems.Columns.Add("customer", "Customer");
DGViewListItems.Columns[0].ReadOnly = true;
DGViewListItems.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
DGViewListItems.Columns[1].ReadOnly = true;
DGViewListItems.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
using (SqlConnection con = db.Connect())
{
SqlDataReader rd;
SqlCommand cmd = new SqlCommand("SELECT DISTINCT custcode, custname FROM Customers WHERE type = 'V';", db.Connect());
rd = cmd.ExecuteReader();
int i = 0;
if (rd.HasRows)
{
while (rd.Read())
{
DGViewListItems.Rows.Add();
DGViewListItems.Rows[i].Cells["custcode"].Value = rd["custcode"].ToString().Trim();
DGViewListItems.Rows[i].Cells["customer"].Value = rd["custname"].ToString().Trim();
}
}
cmd.Connection.Close();
}
}
The SSMS output:
The output form loads slowly, the rows for the query are over 1000 so I think the cause of the slow load is the number of returned rows? If yes, how do make the load of the datagridview faster?
NOTE - this answer relates to the first part of the original question regarding why only a single row of the DataGrid being populated.
Incrementing the counter "i" at the bottom of the while loop looks like it may fix the problem.
You are adding rows, but only updating the first.

For the first TextBox txtName, it is showing a dropdown list but for the other TextBox i am not getting any dropdownlist

Below code is of .net windows form app showing 2 TextBoxes with drop down suggestion when user types something in it. the code for first TextBox txtName is working but the other one is not....i need hellp on this.
private void txtName_TextChanged(object sender, EventArgs e)
{
connection = new SqlConnection();
connection.ConnectionString = "Data Source=DESKTOP-H1PE2LT;Initial Catalog=HealthandWellness;Integrated Security=True";
connection.Open();
cmd = new SqlCommand("SELECT [Medicine Name] FROM Details", connection);
reader = cmd.ExecuteReader();
while (reader.Read())
{
AutoCompleteStringCollection collection = new AutoCompleteStringCollection();
collection.Add(reader["Medicine Name"].ToString());
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
connection = new SqlConnection();
connection.ConnectionString = "Data Source=DESKTOP-H1PE2LT;Initial Catalog=HealthandWellness;Integrated Security=True";
connection.Open();
cmd = new SqlCommand("SELECT [Medicine Name] FROM Details", connection);
reader = cmd.ExecuteReader();
while (reader.Read())
{
AutoCompleteStringCollection collection1 = new AutoCompleteStringCollection();
collection1.Add(reader["Medicine Name"].ToString());
}
}
To say that one of these is working and the other is not leads me to believe that you have set up something in the gui properties because I see nowhere where you set this stringcollection to your autocompletecustomsource. That being said, let's use 2 new textboxes and set up a demo that you can test on your own machine.
For the building of the AutoCompleteStringCollection, I'm going to just use a list but you would use your query. I will try to denote where you would use those methods.
Since you're using the same data for both textboxes, we only need to create the collection once. We'll do this in the form load.
private void Form1_Load(object sender, EventArgs e)
{
//this would be replaced with your query results from the database
string[] input = { "able", "alpha", "baker", "bravo", "charlie", "charcoal", "dog", "delta", "eagle", "foxtrot" };
List<string> pretendThisCameFromAQuery = new List<string>(input);
//define an AutoCompleteStringCollection. Notice it is outside of the loop as you want to add to this with each iteration of the read() or in this case the foreach loop i'm using.
AutoCompleteStringCollection collection = new AutoCompleteStringCollection();
//here is where your While reader.read() would go
foreach (string item in pretendThisCameFromAQuery)
{
collection.Add(item);
//your code here would be something like
//collection.Add(reader["Medicine Name"].ToString());
}
//so now we have a collection. Who wants to use it.
//for txtName1, we'll set it up to just suggest.
txtName1.AutoCompleteCustomSource = collection;
txtName1.AutoCompleteMode = AutoCompleteMode.Suggest;
txtName1.AutoCompleteSource = AutoCompleteSource.CustomSource;
//for textbox2 we'll use suggestappend.
//notice no new collection needed.
textBox2.AutoCompleteCustomSource = collection;
textBox2.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
textBox2.AutoCompleteSource = AutoCompleteSource.CustomSource;
}
Notice that this code is not in any of the textbox events. Especially the TextChanged event as that would run with every change to the textbox and we don't need to do that as we only need to fill the collection and assign it to the textbox(s) that we want to use the collection with before we need to use the dropdown.
Again, if you run this demo, I strongly suggest you add two new textboxes named as I have them named because I really feel you've done something outside of writing code to get the first one to work.

Change text of button when button is clicked c#

First time messing around with c# and I am trying to change the value of a button when the user clicks it.
The database has four names in it, when the code executes it only shows the name that is in the last row. I would like it to start with the first name, and then each time the button is pressed it should show another name until it has gone through all the names in the DB.
dataReader = command.ExecuteReader();
while (dataReader.Read())
{
bText = dataReader.GetValue(1).ToString();
button1.Text = bText;
}
The while loop does not work the way you think it does. What it does is looping over every result and set the button text. It does not pause anywhere (because there is nothing that tells the loop it should). So it simply iterates over every element as fast as it can. This explains why you only see the last result.
What you can do is to store a counter somewhere that you increment every time the button is pressed and only use the n-th element. Either by skipping the first few entries or depending on the database you use, you could only fetch a specific result entry (in MySQL this would be LIMIT and OFFSET in the select query)
For your specific problem (only 4 entries) I would fetch all entries at the start of the application and store them in an array. Then use the entry at the current index (=your counter) to set the text of the button.
You can try this. I've table named Colors having two columns: ID and Name.
public partial class FrmColor : Form
{
SqlConnection con = new SqlConnection(#"<YOUR CONNECTION STRING HERE>");
int pointer = 1;
int rows = 0;
public FrmColor()
{
InitializeComponent();
}
private void btnColor_Click(object sender, EventArgs e)
{
SqlCommand cmd = new SqlCommand("Select Name from Colors Where ID = #ID", con);
cmd.Parameters.AddWithValue("#ID", pointer);
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
rdr.Read();
string colorname = rdr.GetValue(0).ToString();
con.Close();
btnColor.Text = colorname;
if (pointer == rows)
{
pointer = 1;
}
else
{
pointer++;
}
}
private void FrmColor_Load(object sender, EventArgs e)
{
SqlCommand cmd = new SqlCommand("Select count(Name) From Colors", con);
con.Open();
rows = (Int32)cmd.ExecuteScalar();
con.Close();
}
}
Don't forget to add using System.Data.SqlClient;
Hope this helps.

Adding datatable row and column and updating datagridview

string conString = "Server=192.168.1.100;Database=product;Uid=newuser;Pwd=password";
MySqlConnection conn = new MySqlConnection(conString);
DataTable dt = new DataTable();
DataRow row = dt.NewRow();
conn.Open();
//cmd = conn.CreateCommand();
//cmd.CommandText = "Select * From tblindividualproduct";
if (e.KeyCode == Keys.Enter)
{
if (txtBarcode.Text == "")
{
MessageBox.Show("Please Fill the correct ProductID");
}
else
{
string sql = "Select * From tblindividualproduct where ProductID = #ProductIdText";
using (var adapt = new MySqlDataAdapter(sql, conn))
using (var cmd = new MySqlCommandBuilder(adapt)) //Not sure what you need this for unless you are going to update the database later.
{
adapt.SelectCommand.Parameters.AddWithValue("#ProductIdText", txtBarcode.Text);
BindingSource bs = new BindingSource();
adapt.Fill(dt);
bs.DataSource = dt;
dgItems.ReadOnly = true;
dgItems.DataSource = bs;
}
}
}
How do I make the results to add, not just replace the last result. This is the whole code as requested. I just dont know if it needs manually adding of rows or there is an easy way to make it. Thank in advance
First of all, you should use Parameterized SQL to avoid SQL Injection.
Then you just need to put the code you have in an event handler of some sort. I would think the user would probably hit the enter key or something in the TextBox.
As long as you don't clear your DataTable it will just keep adding rows every time you run the query. If you want to clear the table at some other point in your code just call dt.Clear().
Something like this should get what you want and be safe from injection:
private void txtBarCode_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
if(String.IsNullOrEmpty(txtBarcode.Text))
{
MessageBox.Show("Please Fill the correct ProductID");
}
else
{
string sql = "Select * From tblindividualproduct where ProductID = #ProductIdText";
using (var adapt = new MySqlDataAdapter(sql, conn))
using (var cmd = new MySqlCommandBuilder(adapt)) //Not sure what you need this for unless you are going to update the database later.
{
adapt.SelectCommand.Parameters.AddWithValue("#ProductIdText", txtBarCode.Text);
BindingSource bs = new BindingSource();
adapt.Fill(dt);
bs.DataSource = dt;
dgItems.ReadOnly = true;
dgItems.DataSource = bs;
}
}
}
}
Then make sure you have attached this event to your TextBox somewhere in your Form class like this:
this.txtBarCode.KeyUP += txtBarCode_KeyUp;
Now when the user types in the box and presses enter the query will run and the results will display in the DataGridView. No need to manually add columns and rows, the adapt.Fill(dt); Will do that for you.

Efficient way to retrieve multiple values from the same column

I want to set some buttons text (content) with values I retrieve from a mysql.
till now I always did it like this:
if (rdr.Read())
{
Item1.Visibility = Visibility.Visible;
Item1txt.Text = rdr.GetString("item_name");
}
if (rdr.Read())
{
Item2.Visibility = Visibility.Visible;
Item2txt.Text = rdr.GetString("item_name");
}
if (rdr.Read())
{
Item3.Visibility = Visibility.Visible;
Item3txt.Text = rdr.GetString("item_name");
}
This way works fine because I retrieve the right values in each button, but it makes the readability horrible..
When I started this project I had zero knowledge of C# so I tried some things like:
while (rdr.Read())
{
Item4.Visibility = Visibility.Visible;
Item4txt.Text = rdr.GetString("item_name");
Item5.Visibility = Visibility.Visible;
Item5txt.Text = rdr.GetString("item_name");
}
But this gave me the same value retrieved from my database in my buttons..
example:
button 1: test1 | button 2: test1 | button 3: test1.. etc..
what i need:
button 1: test1 | button2: test2 | button 3: test3.. etc..
Now my knowledge of C# is getting better every day so I want to learn some new things.
Right now I'm trying to use the foreach loop but I have a feeling I'm missing something:
using (MySqlConnection conn = new MySqlConnection(_CS))
{
conn.Open();
string cmd = "SELECT * FROM ordertopos LIMIT 10";
MySqlCommand custid = new MySqlCommand(cmd, conn);
using (MySqlDataReader rdr = custid.ExecuteReader())
{
System.Data.DataTable dt = new System.Data.DataTable();
dt.Load(rdr);
foreach (System.Data.DataRow row in dt.Rows)
{
Orderl1.Text = row["customerid"].ToString();
}
}
}
Essentially I want to know how I can set the content of my buttons, retrieved from mysql, in a more efficient en easier to maintain code..
I'm fairly new with foreach, so please be specific.
i would recommend to do it in several step's for a better reusability
Step 1
List<string> myItems;
using (MySqlConnection conn = new MySqlConnection(_CS))
{
conn.Open();
string cmd = "SELECT * FROM ordertopos LIMIT 10";
MySqlCommand custid = new MySqlCommand(cmd, conn);
using (MySqlDataReader rdr = custid.ExecuteReader())
{
myItems= new List<string>();
while (rdr.Read())
{
myItems.Add(rdr.GetString("item_name"));
}
}
}
Step 2
modified Olivier Jacot-Descombe version
for(int i =0; i< myItems.count; i++) {
Button btn = FindChild<Button>(this, "Item" + i); //"this" will be the control which contains your button's
TextBlock tb = FindChild<TextBlock>(btn, "Item" + i + "txt");
btn.Visibility = Visibility.Visible;
tb.Text =myItems[i];
i++;
}
If you want to scale your problem in order to use loops than you need to have a List or an Array that contains objects for which you want to set values of properties. In your particular case, put your Orders in a List<Order> and then you could use something like this:
int count = 0;
foreach (System.Data.DataRow row in dt.Rows)
{
if(count<orders.Count)
orders[count++].Text = row["customerid"].ToString();
}
You need to traverse through your Items to set respective values. As DJ Kraze suggested, you are just overwriting the same control. And it will have the last accessed value (as it won't be overwritten once loop has ended).
You you just need to have somehow reference to your target controls, or if you are creating controls on the fly, than you can simply pass reference of newly created control every time you access a row from database.
You can access controls by their name instead of their member variable by using the FindChild method found here: WPF ways to find controls.
int i = 1;
while (rdr.Read()) {
Button btn = FindChild<Button>(this, "Item" + i);
TextBlock tb = FindChild<TextBlock>(btn, "Item" + i + "txt");
btn.Visibility = Visibility.Visible;
tb.Text = rdr.GetString("item_name");
i++;
}
This enables you to loop through them by using a counter (i).

Categories