Combobox = textbox + list - c#

I want to free type in combobox. When I stop typing I have a delayed task that populates combobox items with some input dependent results. The problem is that my input is overridden by the first item in the list. Is there a way to keep my input?
My sample code is going like this:
public void PopulateCombo(JObject result)
{
Debug.WriteLine("Thread id: " + Thread.CurrentThread.ManagedThreadId);
cbSearch.Items.Clear();
if (result.Value<bool>("success") == true)
{
JArray arr = result.Value<JArray>("data");
for (int i = 0; i < arr.Count; i++)
{
JToken item = arr[i];
cbSearch.Items.Add(new ComboBoxItem( item.Value<string>("name"), item.Value<string>("_id")));
}
cbSearch.DroppedDown = true;
}
}
Edited on 23.06
I'm giving an example of what I'm really trying to do.
Combobox is empty (no items)
User starts typing for example "ja". Combobox sends query to my backend. Should n't be a problem as the call is asynchronous with 1 second delay after user last input.
My backend returns some results (Anton Jamison, James Aaron, James Hetfield, etc., limited to 50)
I want to populate the dropdown list with results, to open it, but as a combobox text i want to keep "ja", so the user can clarify his search further.
User extends his search "ja h". Backend responds with James Hetfield. Result now is only one item and I can set the combobox text now or keep the behavior from above. Not sure which would be better yet.
All this is implemented but at step 4 when I populate the combobox using the function above, the text of the combo is changed from "ja" to the first match of the list. (Anton Jamison in the example). I'm almost sure that there was a simple option for implementing this behavior but I'm not sure if it was in C#.
On comments :
It was a good try but unsuccessful. Once I populate the combobox items my search string is changed to the first match of the list.
I think I don't try to implement the autocomplete feature.
Good catch about the DroppedDown. I move it in the edited version.

I do not have the problem you talked about. The text in the edit box stays the same all the time.
I am using VS2008 though with a standard ComboBox renamed to cbSearch and its event captured (as well as the form's show event).
Rest works nicely.
Seemed like a nice task so I did it.
I also recover the selection, though you can see some flickering.
Most difficult was the synchronization - so I found an easy not tooo ugly solution.
Still, I don't do anything different from you.. maybe you start with a blank ComobBox again, maybe you changed some of the default parameters.
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.Threading;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void cbSearch_TextUpdate(object sender, EventArgs e)
{
lastUpdate = DateTime.Now;
allowUpdate = true;
}
DateTime lastUpdate = DateTime.Now;
volatile bool allowUpdate = false;
private void BoxUpdate()
{
while (true)
{
Thread.Sleep(250);
if (allowUpdate)
{
var diff = DateTime.Now - lastUpdate;
if (diff.TotalMilliseconds > 1500)
{
allowUpdate = false;
this.InvokeEx(x =>
{
if (x.cbSearch.Text.Length > 0)
{
x.PopulateCombo(cbSearch.Text);
}
});
}
}
}
}
public void PopulateCombo(string text)
{
int sStart = cbSearch.SelectionStart;
int sLen = cbSearch.SelectionLength;
List<string> cbItems = new List<string>();
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j)
cbItems.Add(i + text + j);
cbSearch.Items.Clear();
{
for (int i = 0; i < cbItems.Count; i++)
{
cbSearch.Items.Add(cbItems[i]);
}
cbSearch.DroppedDown = true;
}
cbSearch.SelectionStart = sStart;
cbSearch.SelectionLength = sLen;
}
private void Form1_Shown(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(x =>
{
BoxUpdate();
});
}
}
public static class ISynchronizeInvokeExtensions
{
public static void InvokeEx<T>(this T #this, Action<T> action)
where T : System.ComponentModel.ISynchronizeInvoke
{
if (#this.InvokeRequired)
{
#this.Invoke(action, new object[] { #this });
}
else
{
action(#this);
}
}
}
}

Managed to do the same task with the hint of comment 1 + some tweaks. Here is my final code that does the work:
private void cbSearch_TextUpdate(object sender, EventArgs e)
{
timer1.Stop();
timer1.Dispose();
timer1 = null;
timer1 = new System.Windows.Forms.Timer();
timer1.Tick += new EventHandler(timer1_Tick);
timer1.Interval = 1000;
timer1.Start();
}
delegate void MethodDelegate(JObject result);
void timer1_Tick(object sender, EventArgs e)
{
timer1.Stop();
Debug.WriteLine(this.cbSearch.Text);
Debug.WriteLine("Thread id: " + Thread.CurrentThread.ManagedThreadId);
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters["query"] = this.cbSearch.Text ?? "";
this.session.rpc["advanced_search"].execAsync(parameters, results =>
{
this.BeginInvoke(new MethodDelegate(PopulateCombo), new object[] {results.GetResult()});
});
}
public void PopulateCombo(JObject result)
{
Debug.WriteLine("Thread id: " + Thread.CurrentThread.ManagedThreadId);
this.selectedPatientId = "";
string text = cbSearch.Text;
cbSearch.DroppedDown = false;
cbSearch.Items.Clear();
if (result.Value<bool>("success") == true)
{
JArray arr = result.Value<JArray>("data");
for (int i = 0; i < arr.Count; i++)
{
JToken item = arr[i];
cbSearch.Items.Add(new ComboBoxItem( item.Value<string>("name"), item.Value<string>("_id")));
}
try
{
this.cbSearch.TextUpdate -= new System.EventHandler(this.cbSearch_TextUpdate);
cbSearch.DroppedDown = true;
cbSearch.Text = text;
cbSearch.Select(cbSearch.Text.Length, 0);
}
finally {
this.cbSearch.TextUpdate += new System.EventHandler(this.cbSearch_TextUpdate);
}
}
}

Related

When executing second for loop into listbox program populates no data

so the assignment is to read from the student file into an array and read into the answer key array, compare the two and output a grade based on the array comparison.
the issue i'm having is that when i try to load the answer key into it's array it's like its not even getting the data, because all the questions output as wrong.
below is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AshleyBrown_CPT185A01S_Chapter7Lab
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//variables
private const int SIZE = 20; //current # of q's on test
private int index = 0, count = 1; //counter variables
private int wrong = 0, right = 0; //grade variables
these are the arrays that are used for the answers:
//arrays
private char[] studentAnswers = new char[SIZE];
private char[] answerKey = new char[SIZE];
private void calculateBtn_Click(object sender, EventArgs e)
{
//prevents any file errors
try
{
ReadStudentFile();
ReadAnswerKey();
CompareAnswers();
}
catch
{
MessageBox.Show("File doesn't exist or has the wrong name.");
}
}
private void clearBtn_Click(object sender, EventArgs e)
{
//Clear Form
studentAListBox.Items.Clear();
correctAListBox.Items.Clear();
wrongAListBox.Items.Clear();
incorrectBox.Text = "";
correctBox.Text = "";
percentBox.Text = "";
}
private void exitBtn_Click(object sender, EventArgs e)
{
//close program
Close();
}
method for reading the student file that works:
private void ReadStudentFile()
{
//Stream Reader Setup
StreamReader studentFile;
studentFile = File.OpenText("C:\\Users\\aabro\\Documents\\_CPT 185\\AshleyBrown_CPT185A01S_Chapter7Lab\\Student File.txt");
//Read Student Answers into studentAnswers Array
while (index < studentAnswers.Length && !studentFile.EndOfStream)
{
studentAnswers[index] = char.Parse(studentFile.ReadLine());
index++;
}
//Close Student Answer file
studentFile.Close();
//Display Student Answers
foreach (char answer in studentAnswers)
{
studentAListBox.Items.Add(count + ". " + answer.ToString());
count++;
}
}
method for reading answer key that populates with no data:
private void ReadAnswerKey()
{
//Stream Reader Setup
StreamReader answerFile;
answerFile = File.OpenText("C:\\Users\\aabro\\Documents\\_CPT 185\\AshleyBrown_CPT185A01S_Chapter7Lab\\Answer Key.txt");
//Read Answer Key in answerKey Array
while (index < answerKey.Length && !answerFile.EndOfStream)
{
answerKey[index] = char.Parse(answerFile.ReadLine());
index++;
}
//Close answer key file
answerFile.Close();
//clear count
count = 1;
//display answer key in correct answer list box
foreach (char key in answerKey)
{
correctAListBox.Items.Add(count + ". " + key.ToString());
count++;
}
}
private void CompareAnswers()
{
//reset count
count = 1;
for (index = 0; index < answerKey.Length; index++)
{
//determine if answer is right
if (studentAnswers[index] != answerKey[index])
{
wrongAListBox.Items.Add(count + ". " + answerKey[index]);
wrong++;
count++;
}
}
//fail display
if (wrong > 5)
{
MessageBox.Show("Student has failed");
}
//calculations
double pointPerQ = 5;
double wrongTotal = wrong;
double wrongPointTotal = wrong * pointPerQ;
double grade = 100 - wrongPointTotal;
//output grade information
incorrectBox.Text = wrong.ToString();
correctBox.Text = right.ToString();
percentBox.Text = grade.ToString("p0");
}
}
}
this is the current output from running the program, I did double check the file names and contents as well.
output of current code
One issue that I saw in code is using of index in multiple while loops without previously assigning to 0.
I suggest using of local variables(variable which exists only in the current block of code).
Also in my opinion it will be good to declare and initialize new variable in for-loop like this:
for(int index = 0; index < answerKey.Length; index++)
{
// your code
}
it will be more readable and easier if you want later to separate loops in methods or move in service or helper.

values a re not being displayed in TextBox

in this program, when the Recall button (recallBtn_Click()) is clicked, it calls a method (that calculates directions) from another class which should then call the showPath() method. the show path method should then display its output in a textBox. But the values don't show even though i can see from debugging that the values are being sent to the text box. can anybody tell me where i went wrong?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
storeRetSelect.SelectedIndex = 0;
PrioritySelect.SelectedIndex = 0;
}
public void showPath(List<PathFinderNode> mPath)
{
var T = new Form1().directionsTextBox;
foreach (PathFinderNode node in mPath)
{
if ((node.X - node.PX) > 0) { T.Text += "Right" + System.Environment.NewLine ; }
if ((node.X - node.PX) < 0) { T.Text += "Left" + System.Environment.NewLine; }
if ((node.Y - node.PY) > 0) { T.Text += "UP" + System.Environment.NewLine; }
if ((node.Y - node.PY) < 0) { T.Text += "Down" + System.Environment.NewLine; }
}
}
private void recallBtn_Click(object sender, EventArgs e)
{
var path = new pathPlan();
string desigString = inputTextBox.Text;
int[] desig = new int[3];
for (int i = 0; i < desigString.Length; i++) { desig[i] = (int)char.GetNumericValue(desigString[i]); }
path.Recall(desig[1], desig[2], (-1) * desig[0]);
}
}
With this line you are initialising a new object and get the reference of the textbox there.
var T = new Form1().directionsTextBox;
But I assume you want to use the textbox of the form which is allready open. Change the line to the following to access the textbox of the current object.
var T = this.directionsTextBox;

Multithreading code on button click event leaves the application in a hung state

This is my 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;
namespace MteWindowsForm
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
dgv.AutoGenerateColumns = false;
AddCols();
AddRows();
}
void AddRows()
{
dgv.Rows.Add(10000);
bool istrue = true;
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10000; j++)
{
if (istrue)
{
dgv.Rows[j].Cells[i].Value = "SomeText";
istrue = !istrue;
}
else istrue = !istrue;
}
}
}
void AddCols()
{
for (int i = 0; i < 10; i++)
{
DataGridViewColumn dgvCol = new DataGridViewTextBoxColumn();
string strText = "Column_" + i;
dgvCol.Name = strText;
dgvCol.HeaderText = strText;
dgvCol.FillWeight = 1;
dgv.Columns.Add(dgvCol);
}
}
private void button1_Click(object sender, EventArgs e)
{
//lock empty cells
}
}
}
This is what my form looks like:
However on clicking Button1 the application stays like that and does nothing. My algorithm is simple - i am dividing the datagridview columns count from 0->n/2 and n/2+1->n and locking a bunch of columns and unlocking the rest.
Please help.
Yes, your application has got a Deadlock. Your worker thread waits in dgv.Invoke for main thread to complete, at the same time your main thread waits in Join for workers to complete indefinitely hence results in deadlock.
You're using Thread where it is unnecessary. I mean your LockCols method is completely dealing with UI so you must run it in UI. by running the loop in other thread you're not going to get any benefit. Following code is better version of yours.
void LockCols(int istart, int iend, bool isReadOnly)
{
for (int idx = istart; idx < iend; idx++)
{
if (isReadOnly)
{
dgv.Columns[idx].ReadOnly = isReadOnly;
dgv.Columns[idx].HeaderText = dgv.Columns[idx].Name + "_" + "ReadOnly";
}
else
{
dgv.Columns[idx].ReadOnly = isReadOnly;
dgv.Columns[idx].HeaderText = dgv.Columns[idx].Name + "_" + "Not_ReadOnly";
}
}
}
private void button1_Click(object sender, EventArgs e)
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
LockCols(0, dgv.Columns.Count / 2, true);
LockCols(dgv.Columns.Count / 2 + 1, dgv.Columns.Count, false);
sw.Stop();
MessageBox.Show(sw.Elapsed.ToString());
}
There's the potential deadlock mentioned in the comments, and while I'm not sure if that is the case, the hanging of the application is caused by the lines
t1.Join();
t2.Join();
Join is telling the UI thread that it should block until these threads are finished. You either need a separate callback/event to fire when the threads complete, or, if you're using .NET 4.5, you can use the Task Async capabilities and use await on your background thread calls.
Check out this example on how to use the new async/await keywords.

c# - ArgumentOutOfRangeException: Index was out of range - 2 Forms

Problem solved.
The original "private void buttonSave_Click" was changed to:
private void buttonSave_Click(object sender, EventArgs e)
{
if (MusicCollection.FormMain.PublicVars.AlbumList.Count != 100)
{
MusicCollection.FormMain.PublicVars.AlbumList.Add(new Album(NameTextBox.Text));
MessageBox.Show("New Album added: " + NameTextBox.Text);
formMain.ListAlbums(formMain.AlbumsListBox.Items);
this.Close();
}
else
{
MessageBox.Show("No room for new album.");
this.Close();
}
}
Original Post:
I'm new to using C#, so appologies for any seemly obvious mistakes or terrible coding.
I'm trying to create a new Album object (that gets its Name from NameTextBox.Text on Form FormAlbumAC) and add it to List AlbumList when the user clicks the save button on FormAlbumAC. Then I want to list all of AlbumList in a ListBox on Form FormMain.
When I run the program and click the save button, I'm getting the error "ArgumentOutOfRangeException was unhandled, Index was out of range" at the line:
if (MusicCollection.FormMain.PublicVars.AlbumList[i] == null)
// line 8 on my excerpt from Form FormAblumAC
I'm not sure what I'm doing wrong. Any help would be much appreciated, thank you.
Form FormMain:
public const int MAX_ALBUMS = 100;
public int totalAlbums = 0;
public FormMain()
{
InitializeComponent();
}
public static class PublicVars
{
public static List<Album> AlbumList { get; set; }
static PublicVars()
{
AlbumList = new List<Album>(MAX_ALBUMS);
}
}
public ListBox AlbumListBox
{
get
{
return AlbumListBox;
}
}
public void ListAlbums(IList list)
{
list.Clear();
foreach (var album in PublicVars.AlbumList)
{
if (album == null)
continue;
list.Add(album.Name);
}
}
Form FormAlbumAC:
private FormMain formMain;
private void buttonSave_Click(object sender, EventArgs e)
{
int index = -1;
for (int i = 0; i < MusicCollection.FormMain.MAX_ALBUMS; ++i)
{
if (MusicCollection.FormMain.PublicVars.AlbumList[i] == null)
{
index = i;
break;
}
}
if (index != -1)
{
MusicCollection.FormMain.PublicVars.AlbumList[index] = new Album(NameTextBox.Text);
++formMain.totalAlbums;
MessageBox.Show("New Album added: " + NameTextBox.Text);
formMain.ListAlbums(formMain.AlbumsListBox.Items);
this.Close();
}
else
{
MessageBox.Show("No room for new album.");
this.Close();
}
}
Your problem (from your comments) is that your for loop's condition is incorrect. Your for loop is this:
for (int i = 0; i < MusicCollection.FormMain.MAX_ALBUMS; ++i)
There is one problem and one potential problem here. First, when this code is actually run, it's really running:
for (int i = 0; i < 100; ++i)
because MusicCollection.FormMain.MAX_ALBUMS is declared as 100. This causes an error when the length of MusicCollection.FormMain.PublicVars.AlbumList is less than 100, because you're trying to grab an index that doesn't exist.
Instead, you need to iterate from i=0 to the length of ....PublicVars.AlbumList-1, or, preferably, for(int i = 0; i < ....PublicVars.AlbumList.Count; i++).
The second potential problem is that you are potentially skipping index 0. Arrays start at index zero and continue to index length-1. As such, you probably want i++, not ++i. Depends on your implementation, though.

How can I correct the error "accessed from a thread other than the thread it was created on"?

This following code gives me the error below . I think I need "InvokeRequired" . But I don't understand how can I use?
Cross-thread operation not valid: Control 'listBox1' accessed from a thread other than the thread it was created on.
The code:
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace WindowsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected static DataSet dataset = null;
private void Form1_Load(object sender, EventArgs e)
{
}
private void timer1_Tick(object sender, EventArgs e)
{
SimulationFrameWork.MCSDirector run = new SimulationFrameWork.MCSDirector();
DataSet ds = run.Get();
if (ds.Tables[0].Rows.Count > 0)
{
for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
{
if (ds.Tables[0].Rows[i]["result"].ToString() == "0")
{
dataset = run.Get(int.Parse(ds.Tables[0].Rows[i]["ID"].ToString()));
WorkerObject worker =
new WorkerObject(
int.Parse(dataset.Tables[0].Rows[i]["ID"].ToString()),
int.Parse(dataset.Tables[0].Rows[i]["Iteration"].ToString()),
listBox1, timer1);
Thread thread1 = new Thread(new ThreadStart(worker.start));
thread1.Start();
}
}
}
}
}
public class WorkerObject
{
private int id;
private int nmax;
private ListBox list1;
private System.Windows.Forms.Timer timer1;
public WorkerObject(int _id, int _nmax, ListBox _list1,
System.Windows.Forms.Timer _timer1)
{
id = _id;
nmax = _nmax;
list1 = _list1;
timer1 = _timer1;
}
public void start()
{
timer1.Stop();
int i, idaire, j;
double pi = 0.0, x, y;
Random rnd = new Random();
for (i = 0; i < 100; i++)
{
idaire = 0;
for (j = 0; j < nmax; j++)
{
x = rnd.Next(1, 10000) / (double)10000;
y = rnd.Next(1, 10000) / (double)10000;
if (Math.Pow(x, 2) + Math.Pow(y, 2) <= 1.0)
idaire += 1;
}
pi = 4 * (double)idaire / (double)nmax;
nmax *= 10;
list1.Items.Add(
"Iterasyon:" +
nmax.ToString() +
" ----->" +
pi.ToString() +
"\n");
System.Threading.Thread.Sleep(100);
}
SimulationFrameWork.MCSDirector run = new SimulationFrameWork.MCSDirector();
run.Update(id, pi);
list1.Items.Add("\n\n islem bitti...");
}
}
}
This should get you around it
private delegate void stringDelegate(string s);
private void AddItem(string s)
{
if (list1.InvokeRequired)
{
stringDelegate sd = new stringDelegate(AddItem);
this.Invoke(sd, new object[] { s });
}
else
{
list1.Items.Add(s);
}
}
Just call AddItem and this will invoke the add using a delegate if it is required otherwise it will just add the item directly to the box.
OneSHOT
Just encapsulate adding the text to the listbox to another method:
private void timer1_Tick(object sender, EventArgs e)
{
// ...
AddTextToListBox("\n\n işlem bitti...");
}
private void AddTextToListBox(string text)
{
if(list1.InvokeRequired)
{
list1.Invoke(new MethodInvoker(AddTextToListBox), new object[] { text });
return;
}
list1.Items.Add(text);
}
Can also use lambda notation. So, instead of:
formControl.Field = newValue; //Causes error
Try:
Invoke(new Action(() =>
{
formControl.Field = newValue; //No error
}));
Add this code before starting the thread:
//kolay gelsin kardeş )
CheckForIllegalCrossThreadCalls = false;
thread1.Start();

Categories

Resources