I have a problem with binding to multiple controls. For example I have different controls: 1 listbox, 2 comboboxes, 1 datagridview and all of them have a binding to the same list or iobservablecollection (and except the datagridview display the same property). Everything works fine. But, if I change the selection of one control, the selection of all the other controls change to the same item. Or in other words, if I change the selection of a combobox to item Number 5, the other combobox changes it's selection to Number 5 as well and the listbox changes it's selection also to item Number 5. Does anybody know how to avoid this?
edit:
Here's sample code that leads to the same behavior:
private List TestList;
private void Form1_Load(object sender, EventArgs e)
{
TestList = new List<foo>();
for (int i = 0; i <= 5; i++)
{
foo f = new foo();
f.test = "Test_" + i;
TestList.Add(f);
}
comboBox1.DataSource = TestList;
comboBox1.DisplayMember = "test";
comboBox2.DataSource = TestList;
comboBox2.DisplayMember = "test";
}
class foo
{
private string _test;
public string test
{
get
{
return _test;
}
set
{
_test = value;
}
}
}
If I change the ite of combobox1 the item of combobox2 changes automatically to the same item.
picture: 1
I would like to see your controls but your datasources for both
comboBox1.DataSource = TestList; and comboBox2.DataSource = TestList; are the same. So when you change something in one its going to affect the other.
I found at least a temporary solution that does not show this behavior.
from:
combobox1.DataSource = TestList;
to:
combobox1.DataSource = Testlist.ToList();
For all who have the same problem, please have a look at these links:
https://blogs.msdn.microsoft.com/bethmassi/2007/09/19/binding-multiple-combo-boxes-to-the-same-data-source/
and
One DataSource for multiple controls
Related
I have this datagrid where Combobox is populated from Db.
What I'm trying to achieve is that when I select something in the column "Esercizio", the cell of "Video" column auto populate with respective value from the "link_video" column of Db.
So if I select "kickback", I need to see the link video of kickback from db in the textbox cell.
Here's the code that i use to populate the combobox on form load:
private void Myform_Load(object sender, EventArgs e)
{
con = new SqlConnection("Data Source=(LocalDB)\\etc");
cmd = new SqlCommand();
con.Open();
cmd.Connection = con;
cmd.CommandText = "SELECT * FROM Esercizi";
dr = cmd.ExecuteReader();
while (dr.Read())
{
//populate Column1 combobox with "nome" column from Esercizi db table
Column1.Items.Add(dr["nome"]);
}
con.Close();
}
datagridview
EDIT
I've figured out with 2 new problems.
I'm trying to load a saved workout from db but when I do this, no video link populate the dgv as the grid event doesn't fire.
What I've tried is to add a foreach loop to a new selectionindexchanged function and to fire it at the end of the Load Button code like this:
private void curCombo_LoadedValues(object sender, EventArgs e)
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
foreach (DataGridViewCell cell in row.Cells)
{
if (curCombo != null && curCombo.SelectedValue != null)
{
ExerciseAndVideo selectedExercise = (ExerciseAndVideo)curCombo.SelectedItem;
dataGridView1.CurrentRow.Cells["Video"].Value = selectedExercise.Video;
}
}
}
}
private void button9_Click(object sender, EventArgs e){
string connectionString = "Data Source=(LocalDB)\\etc";
string sql = "SELECT * FROM Schede WHERE Id = 6 AND dgv = 'dataGridView1'";
SqlConnection connection = new SqlConnection(connectionString);
SqlDataAdapter dataadapter = new SqlDataAdapter(sql, connection);using (DataTable dt = new DataTable())
{
dataadapter.Fill(dt);
//Set AutoGenerateColumns False
dataGridView1.AutoGenerateColumns = false;
//Set Columns Count
dataGridView1.ColumnCount = 6;
//Add Columns
dataGridView1.Columns[0].Name = "Esercizio";
dataGridView1.Columns[0].HeaderText = "Esercizio";
dataGridView1.Columns[0].DataPropertyName = "Esercizio";
dataGridView1.Columns[1].Name = "Serie";
dataGridView1.Columns[1].HeaderText = "Serie";
dataGridView1.Columns[1].DataPropertyName = "Serie";
dataGridView1.Columns[2].HeaderText = "Ripetizioni";
dataGridView1.Columns[2].Name = "Ripetizioni";
dataGridView1.Columns[2].DataPropertyName = "Ripetizioni";
dataGridView1.Columns[3].Name = "Recupero";
dataGridView1.Columns[3].HeaderText = "Recupero";
dataGridView1.Columns[3].DataPropertyName = "Recupero";
dataGridView1.Columns[4].Name = "Time Under Tension";
dataGridView1.Columns[4].HeaderText = "Time Under Tension";
dataGridView1.Columns[4].DataPropertyName = "Time_Under_Tension";
dataGridView1.DataSource = dt;
connection.Close();
}
curCombo_LoadedValues();
}
But I get this error "the are no arguments for the obligatory parameter sender...
How I can call it correctly?
The second Issue is that when I populate some dgv columns like this, combos stops working correctly and I get an error exception on the combobox :
dataGridView1.Rows.Add(7);
Random rnd = new Random();
dataGridView1.Rows[0].Cells[1].Value = 3;
dataGridView1.Rows[0].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[0].Cells[3].Value = 1;
dataGridView1.Rows[0].Cells[4].Value = 201;
dataGridView1.Rows[1].Cells[1].Value = 2;
dataGridView1.Rows[1].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[1].Cells[3].Value = 1;
dataGridView1.Rows[1].Cells[4].Value = 201;
dataGridView1.Rows[2].Cells[1].Value = 3;
dataGridView1.Rows[2].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[2].Cells[3].Value = 1;
dataGridView1.Rows[2].Cells[4].Value = 201;
dataGridView1.Rows[3].Cells[1].Value = 4;
dataGridView1.Rows[3].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[3].Cells[3].Value = 1;
dataGridView1.Rows[3].Cells[4].Value = 201;
dataGridView1.Rows[4].Cells[1].Value = 5;
dataGridView1.Rows[4].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[4].Cells[3].Value = 1;
dataGridView1.Rows[4].Cells[4].Value = 201;
dataGridView1.Rows[5].Cells[1].Value = 6;
dataGridView1.Rows[5].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[5].Cells[3].Value = 1;
dataGridView1.Rows[5].Cells[4].Value = 201;
dataGridView1.Rows[6].Cells[1].Value = 7;
dataGridView1.Rows[6].Cells[2].Value = rnd.Next(1, 13);
dataGridView1.Rows[6].Cells[3].Value = 1;
dataGridView1.Rows[6].Cells[4].Value = 201;
This is the look of the dgv now:
dgv
And this is the error the i get after combos stop working correctly (I click and no dropdown appear or if I click 2-3 times, a random item get selected but no video link appear in the other column):
error
In the posted code, it shows a query to a DB to get the exercise names and adds those names as the items to display in “each” combo box cell in that column. This is fine, however, there is zero (0) information about “which video” belongs to each of the items in the combo boxes list of items. I will assume the “relation” to which video is related to which exercise would involve another query to the DB.
If this is the case, then it is easy to see that when a combo box in the grid is changed, that you could simply query the DB for which video to use for the selected exercise. This would certainly work; however, it creates a redundancy issue. Example, let’s say the user selected “kicks” in the combo box in row 1 in the gird. The code queries the DB and gets the location of the “kicks” video and sets the “video” cell to this videos path. Then later, the user selects “kicks” again for some other row. This will re-query the DB for the SAME data we previously got. You want to avoid querying the DB unnecessarily.
So given this, it appears a better approach to avoid re-querying the DB unnecessarily, is that we somehow “combine” the exercise with the particular video that the exercise uses. You could do this ONCE when the form loads. Once you have the exercises, then loop though each exercise and query the DB for that exercises video and combine this with the exercise. Once we have the video link, we “save” this info. With this approach, you will not have to re-query the DB for any given exercise since we have saved that info for all exercises.
There are a myriad number of ways to “combine” the exercise with the video. One possible solution is to create a Class that has these two properties. Let’s call this Class ExerciseAndVideo… it has two properties, a string Exercise that is the exercise text and a string Video that defines the path to the video for that Exercise. This simple class may look something like…
public class ExerciseAndVideo {
public string Exercise { get; set; }
public string Video { get; set; }
}
This is one approach to “combine” an Exercise with a particular Video. We could then make a list of ExerciseAndVideo objects like…
List<ExerciseAndVideo> ExerciseList;
Then we cannot only use this list as a DataSource to the combo box column, but we could also use this list as a way to easily tell “which” video belongs to “which” exercise. The example below uses this strategy.
How to implement this in the DataGridView…
One thing to keep in mind is that a DataGridViewComboBoxCell is “different monster” than a “regular” ComboBox. Example; for a regular combo box, there is an event called… SelectedIndexChanged event. This event fires when the user “changes” the combo boxes currently selected index. On the other hand, a DataGridViewComboBoxCell does NOT have a SelectedIndexChanged event, which I assume you may be aware of. The grid’s absence of this event makes sense, because the grid’s combo box “column” may have MANY combo boxes in it.
Fortunately, even though the DataGridViewComboBoxCell does not have a SelectedIndexChanged event, we CAN cast an “individual” combo box cell to a ComboBox (in this case the combo box cell being edited), THEN we CAN subscribe to that ComboBoxes SelectedIndexChanged event. Then we could simply wait until the SelectedIndexChanged event fires then update the video cell data with the appropriate video link.
The DataGridView provides a special event for this called… EditingControlShowing. This event will fire when the user starts to “edit” a cell in the grid. In this particular case, this event would fire when the user clicks into a combo box cell and “starts” to change the cells value. Is what we want to do in this event is simply cast that combo box cell to a regular ComboBox…, THEN, subscribe to that ComboBoxes SelectedIndexChanged event which we will implement below.
The strategy goes like this… we will make a “global” ComboBox variable we will call curCombo. When the grids EditingControlShowing event fires and we see that the edited cell is an “Exercise” cell… Then we will cast THAT combo box cell in the grid to the global curCombo variable. Then we will subscribe to that combo boxes SelectedIndexChanged event. When the user “leaves” the cell we will unsubscribe from the global variables curCombo SelectedIndexChanged event to keep things clean.
Therefore, given this, the grids EditingControlShowing event may look something like below...
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
if (dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name == "Exercise") {
curCombo = e.Control as ComboBox;
if (curCombo != null) {
curCombo.SelectedIndexChanged -= new EventHandler(curCombo_SelectedIndexChanged);
curCombo.SelectedIndexChanged += new EventHandler(curCombo_SelectedIndexChanged);
}
}
}
Obviously we need to implement the event handler curCombo_SelectedIndexChanged. In this event, we would know that previously, the user selected a combo box cell and has changed the value in that cell to some other value. Since the Exercise changed, we know we need to change the “Video” cell.
Again there are numerous ways you could do this, however, if we set the grid’s “Exercise” combo box column’s DataSource as a List of ExerciseAndVideo objects, then we should be able to get that particular ExerciseAndVideo object directly from the global ComboBox curCombo. This will tell us “which” video to place in the “Video” cell. This may look something like…
private void curCombo_SelectedIndexChanged(object sender, EventArgs e) {
if (curCombo != null && curCombo.SelectedValue != null) {
ExerciseAndVideo selectedExercise = (ExerciseAndVideo)curCombo.SelectedItem;
dataGridView1.CurrentRow.Cells["Video"].Value = selectedExercise.Video;
}
}
private void dataGridView1_CellLeave(object sender, DataGridViewCellEventArgs e) {
if (dataGridView1.Columns[e.ColumnIndex].Name == "Exercise") {
if (curCombo != null) {
curCombo.SelectedIndexChanged -= new EventHandler(curCombo_SelectedIndexChanged);
}
}
}
To complete the example and putting all this together may produce something like below…
List<ExerciseAndVideo> ExerciseList;
ComboBox curCombo;
public Form2() {
InitializeComponent();
}
private void Form2_Load(object sender, EventArgs e) {
ExerciseList = GetExerciseVideoComboBoxListFromDB();
dataGridView1.Columns.Add(GetExcerciseComboBoxColumn(ExerciseList));
dataGridView1.Columns.Add(GetLinkColumn());
dataGridView1.CellLeave += new DataGridViewCellEventHandler(dataGridView1_CellLeave);
dataGridView1.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
}
private DataGridViewComboBoxColumn GetExcerciseComboBoxColumn(List<ExerciseAndVideo> exerciseData) {
DataGridViewComboBoxColumn cbCol = new DataGridViewComboBoxColumn();
cbCol.HeaderText = "Exercise";
cbCol.Name = "Exercise";
cbCol.DisplayMember = "Exercise";
cbCol.DataSource = exerciseData;
return cbCol;
}
private DataGridViewLinkColumn GetLinkColumn() {
DataGridViewLinkColumn col = new DataGridViewLinkColumn();
col.HeaderText = "Video";
col.Name = "Video";
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
return col;
}
private List<ExerciseAndVideo> GetExerciseVideoComboBoxListFromDB() {
List<ExerciseAndVideo> exerciseList = new List<ExerciseAndVideo>();
exerciseList.Add(new ExerciseAndVideo { Exercise = "Crosses", Video = #"C:/somepath/Crosses.htm/" });
exerciseList.Add(new ExerciseAndVideo { Exercise = "Kickback", Video = #"C:/somepath/Kickback.htm" });
exerciseList.Add(new ExerciseAndVideo { Exercise = "Leg Extensions", Video = #"C:/somepath/LegExtensions.htm" });
exerciseList.Add(new ExerciseAndVideo { Exercise = "Crunches", Video = #"C:/somepath/Crunches.htm" });
exerciseList.Add(new ExerciseAndVideo { Exercise = "Pushups", Video = #"C:/somepath/Pushups.htm" });
return exerciseList;
}
What if the grid has a data source?
This works as expected when the grid has no data source. However, if the grid has a data source and one of the columns/properties in the data source is bound to our “Exercise” combo box column, then, is what will happen… is that after the data is loaded, the combo boxes should display the proper exercise, however, all the video cells will remain empty. This is obviously because the grid events are not fired when the data is loaded.
So, in that case, you will want a method that loops through all the rows in the grid, checks what the exercise value is for that row, then set the video cell value to the correct video link. Since you do not say if the grid does or does not have a data source, I will assume this is all you need. If there is a data source, I recommend you check each “Exercise” to make sure the “Exercises” in the data are in the combo boxes list of items, if one or more “Exercises” are in the data that are not in the combo box columns list of items, then you will get the grids DataError when you attempt to set the grids data source.
I hope this makes sense.
Edit... An example of setting the video cell after the data has been loaded into the grid.
private void SetVideoCellsAfterDataLoad() {
foreach (DataGridViewRow row in dataGridView1.Rows) {
if (!row.IsNewRow && row.Cells["Exercise"].Value != null) {
foreach (ExerciseAndVideo eav in ExerciseList) {
if (row.Cells["Exercise"].Value.ToString() == eav.Exercise) {
row.Cells["Video"].Value = eav.Video;
break;
}
}
}
}
}
I mixed ComboBox with DatagridViewComboboxColumn.
It's partly your fault :).
Here you have a form with events. Since CellValueChanged fires on cell exit, I added a Dirty StateEvent to update the Video column.
From the designer, just put the datagrid in the form and make sure the name is the same.
IMHO, these 3 events are crucial
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Init();
}
private void Init()
{
var list = new List<Exercise>() {
new Exercise (){Name="Name1", Link= "Link1" },
new Exercise (){Name="Name3", Link= "Link3" },
new Exercise (){Name="Name4", Link= "Link4" },
};
var comboColumn = new DataGridViewComboBoxColumn() { Name = "ExerciseName", CellTemplate = new DataGridViewComboBoxCell() };
comboColumn.DisplayMember = nameof(Exercise.Name);
comboColumn.ValueMember = nameof(Exercise.Link);
comboColumn.DataSource = list;
dataGridView1.Columns.Add(comboColumn);
dataGridView1.Columns.Add(new DataGridViewTextBoxColumn() { Name = "Video" });
dataGridView1.CellContentClick += DataGridView1_CellContentClick;
dataGridView1.CellValueChanged += DataGridView1_CellValueChanged;
dataGridView1.CurrentCellDirtyStateChanged += DataGridView1_CurrentCellDirtyStateChanged;
}
private void DataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
var currentCell = (sender as DataGridView).CurrentCell;
if(currentCell.ColumnIndex == 0)
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex != 0)
return;
var comboCell = (dataGridView1.Rows[e.RowIndex].Cells[0] as DataGridViewComboBoxCell);
var value = comboCell.Value;
dataGridView1.Rows[e.RowIndex].Cells["Video"].Value = value;
}
private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
}
}
public class Exercise
{
public string Name { get; set; }
public string Link { get; set; }
}
Need some help with adding filter to my ComboBox drop down list(windows Forms Visual studio 2015)
The drop down is populated as per below:
public ReconciliationReport()
{
InitializeComponent();
AppDomain.CurrentDomain.AssemblyResolve += FindDLL;
this.sRootDirectory = Properties.Resources.sRootDirectory;
string[] arrProjectList = Directory.GetDirectories(sRootDirectory).Select(Directory => Path.GetFileName(Directory)).ToArray();
Array.Sort(arrProjectList);
int iProjectCount = arrProjectList.Length;
this.DropDownListSize = iProjectCount;
for (int i = 0; i < iProjectCount; i++)
{
SelectJobDropdown.Items.Add(arrProjectList[i]);
}
}
This gives me a nice drop down list of all current directories.
Now, I need to add a filer to show only items which contain a text typed into the ComboBoxitself regardless if the dropdown list itself is open or not.
I have disabled both AutoCompleteMode and AutoCompleteSource as it was not working as expected with the opened droped down list. It was opening additonal list on top the existing one but I could only select from the dropdown under it. See print screen below:
The list on top is inactive and I cannot select the text but also does not give an option to display substrings.
Only have one even for the box itself which is
private void SelectJobDropdown_SelectedIndexChanged(object sender, EventArgs e)
{
//Plenty of code here
}
Can someone point in the right direction how to filter the list as I type within the box itself.
Please NOTE I have been using C# for only 3 weeks so might get confused with some of the terminology or other aspects of this language etc.
I would suggest to use 2 Lists. 1 for the original values
List<string> arrProjectList;
public ReconciliationReport()
{
InitializeComponent();
AppDomain.CurrentDomain.AssemblyResolve += FindDLL;
this.sRootDirectory = Properties.Resources.sRootDirectory;
arrProjectList = Directory.GetDirectories(sRootDirectory).Select(Directory => Path.GetFileName(Directory)).ToList();
arrProjectList.Sort();
// then just bind it to the DataSource of the ComboBox
SelectJobDropdown.DataSource = arrProjectList;
// don't select automatically the first item
SelectJobDropdown.SelectedIndex = -1;
}
and 1 for the filtered values. In this example I use a TextBox to catch the filter text. In the TextChanged event take the filter text and pull out only those values from the original arrProjectList List. You would need an extra option at the end to reset the binding to the old list if the filter is empty.
private void textBox1_TextChanged(object sender, EventArgs e)
{
string filter_param = textBox1.Text;
List<string> filteredItems = arrProjectList.FindAll(x => x.Contains(filter_param));
// another variant for filtering using StartsWith:
// List<string> filteredItems = arrProjectList.FindAll(x => x.StartsWith(filter_param));
comboBox1.DataSource = filteredItems;
// if all values removed, bind the original full list again
if (String.IsNullOrWhiteSpace(textBox1.Text))
{
comboBox1.DataSource = arrProjectList;
}
// this line will make sure, that the ComboBox opens itself to show the filtered results
}
EDIT
I found a Solution for typing the filter into the ComboBox directly. The filtering is the same procedure, but using the TextUpdate event it is necessary to unselect the SelectedIndex which is automatically set to the first element after the binding. Then I guess you want to proceed to write your filter (more than just one letter), write the filter back into the ComboBox.Text property and set the cursor position to the end:
private void comboBox1_TextUpdate(object sender, EventArgs e)
{
string filter_param = comboBox1.Text;
List<string> filteredItems = arrProjectList.FindAll(x => x.Contains(filter_param));
// another variant for filtering using StartsWith:
// List<string> filteredItems = arrProjectList.FindAll(x => x.StartsWith(filter_param));
comboBox1.DataSource = filteredItems;
if (String.IsNullOrWhiteSpace(filter_param))
{
comboBox1.DataSource = arrProjectList;
}
comboBox1.DroppedDown = true;
// this will ensure that the drop down is as long as the list
comboBox1.IntegralHeight = true;
// remove automatically selected first item
comboBox1.SelectedIndex = -1;
comboBox1.Text = filter_param;
// set the position of the cursor
comboBox1.SelectionStart = filter_param.Length;
comboBox1.SelectionLength = 0;
}
Et voilà automatic filtering with a nice display and arrow selection afterwards.
EDIT 2
for case insensitive search you can use this:
List<string> filteredItems = arrProjectList.FindAll(x => x.ToLower().Contains(filter_param.ToLower()));
NOTE:
After the opening of the dropdownlist the cursor will disappear. To prevent this the Cursor.Current has to be set to Cursor.Defualt
comboBox1.DroppedDown = true;
Cursor.Current = Cursors.Default;
In case you are using a Dictionary as data source, the following can be useful.
I created a static function in the form to be able to reuse it as I have several ComboBox instances for which I wanted to have the same behavior.
Simply call the function from the TextUpdate event passing the control name and the source dictionary.
private static void FilterComboBox(ComboBox combo, Dictionary<int, string> dataSource)
{
var filter = combo.Text;
if (string.IsNullOrWhiteSpace(filter))
return;
var filteredItems = dataSource.Where(kv => kv.Value.Contains(filter)).ToDictionary(k => k.Key, k => k.Value);
combo.DisplayMember = "Value";
combo.ValueMember = "Key";
combo.DataSource = new BindingSource(filteredItems, null);
// this will ensure that the drop down is as long as the list
combo.IntegralHeight = false;
combo.IntegralHeight = true;
combo.DroppedDown = true;
// remove automatically selected first item
combo.SelectedIndex = -1;
combo.Text = filter;
// set the position of the cursor
combo.SelectionStart = filter.Length;
combo.SelectionLength = 0;
}
So I have a DataGridView which I am using as a “row selector” on a form, and a bunch of controls are bound to the bindingSource.
One of the bound controls is a ComboBox serving as a lookup which enables status choices for the rows, this is populated from a DataTable with data pulled from the DB.
The population of this box is without any issue.
When a given row is selected from the DGV the form controls display data from the given row as they should, however the “statusComboBox” is not quite playing the game.
If in the DGV, I choose a row that has a different status to one previously selected, it works as it should, however, if I choose a row with the same value to a previously selected row, instead of the box showing the DisplayMember it shows the ValueMember.
IT only seems to occur in the above scenario, where the rows selection only instigates a display response from the bound ComboBox providng a previous selection had a different “Status ID”. What have I dont wrong that would cause this behaviour?
So the form load looks like this
private void ProjectsForm_Load(object sender, EventArgs e)
{
InitBindingSource();
//// bind Selector
//ASMod$ this needs to be 'true' unless you explicitly declare columns
ProjectsDataGridView.AutoGenerateColumns = false;
ProjectsDataGridView.DataSource = ProjectsBindingSource;
GetData();
//Set GeneralStatusBox
Helpers.GeneralStatusInitLookup(statusComboBox, ProjectsBindingSource);
}
The ProjectBindingSource is initialised thus:
private void InitBindingSource()
{
ProjectsBindingSource = new BindingSource();
projectsBindingNavigator.BindingSource = ProjectsBindingSource;
ProjectsBindingSource.PositionChanged += new EventHandler(ProjectsBindingSource_PositionChanged);
}
A ProjectsAddDataBindings procedure, and the contained DataBindings.Add for the ComboBox (executed at the end of a GetData routine that additionally populated ProjectsBindingSource):
ProjectsAddDataBindings();
{
…
this.statusComboBox.DataBindings.Add("Text", ProjectsBindingSource, "GSID");
…
}
After the GetData block the GeneralStatusInitLookup populates the Lookup elements, in a helper class simply because it provides functionality to a number of different forms
public static void GeneralStatusInitLookup(System.Windows.Forms.ComboBox comboBox, BindingSource primaryBindingSource)
{
string statusFilter = "";
statusFilter = Helpers.GetStatusGroupFilter(EndeavourForm.FilterId);
if (statusFilter != "")
{
statusFilter = " WHERE " + statusFilter;
}
//// string statusFilter = ""; //// temp
string sql = "";
sql = "SELECT GSID, ShortName FROM GeneralStatus" + statusFilter + " ORDER BY Pos";
GeneralStatusDataTable = Helpers.Db.GetDataTable(sql);
comboBox.DataSource = GeneralStatusDataTable;
comboBox.DisplayMember = "ShortName";
comboBox.ValueMember = "GSID";
comboBox.DataBindings.Add(new Binding("SelectedValue", primaryBindingSource.DataSource, "GSID"));
}
And the DGV initiated row change is handled like this
private void ProjectsBindingSource_PositionChanged(object sender, EventArgs e)
{
try
{
// Update the database with the user's changes.
UpdateProjects();
statusComboBox.SelectedValue = (int)CurrentDataRowView.Row["GSID"];
}
catch (Exception)
{
}
}
private void UpdateProjects()
{
try
{
ProjectsDataAdapter.Update((DataTable)ProjectsBindingSource.DataSource);
DataHelper.CommitProposedChanges(projectsDataSet);
if (this.projectsDataSet.HasChanges() == true)
{
ProjectsBindingSource.EndEdit();
ProjectsDataAdapter.Update();
}
CurrentDataRowView = (DataRowView)ProjectsBindingSource.Current;
}
catch (InvalidOperationException)
{
throw;
}
catch (Exception)
{
throw;
}
}
Anyway I hope I haven't swamped readers with to much code, but frankly I cant see where this is going wrong. So any help would be greatly appreciated.
This was a simple solution in the end. Both the GeneralStatusInitLookup() and the ProjectsAddDataBindings() blocks made use of DataBindings.Add ... For the lookup table this was fine, but with the binding to the main table; the later, I had used "Text" as the propertyName parameter.
I have two Lists, the firs (right) represents a list of all cars and the secound (left) a list of cars for sale.
There are two ListBoxes at my control, each of them will display one of the cars list. I want to click a button (<<) and the selected car at the ListBox 1 will be added to the ListBox 1 list.
The two lists are created outside the control class, so I need to bind then to the ListBoxes. I have tried to use DataSource but I can not use Add Remove from Items if I set it. Which is the best way to do that?
Thanks.
OBS: Ive changed to a ListBox.
The solution that I'm using is: Very very ugly solution... :/
public IList<Item> ItensToMaintaim
{
get { return (IList<Item>)this.itensToMainTainList.DataSource; }
set
{
//Need to set null to refresh
this.itensToMainTainList.DataSource = null;
this.itensToMainTainList.DataSource = value;
this.itensToMainTainList.DisplayMember = "Name";
this.itensToMainTainList.ValueMember = "Name";
}
}
public IList<Item> Itens
{
get { return (IList<Item>)this.itensList.DataSource; }
set
{
//Need to set null to refresh
this.itensList.DataSource = null;
this.itensList.DataSource = value;
this.itensList.DisplayMember = "Name";
this.itensList.ValueMember = "Name";
}
}
private void removeItem_Click(object sender, EventArgs e)
{
if (this.itensToMainTainList.SelectedItem != null)
{
this.itens2.Remove((Item)this.itensToMainTainList.SelectedItem);
this.ItensToMaintaim = this.itens2;
if (this.itensToMainTainList.SelectedIndex < 0)
{
this.itensToMainTainList.SelectedIndex = this.itens2.Count - 1;
}
}
}
private void addItem_Click(object sender, EventArgs e)
{
if (this.itensList.SelectedItem != null)
{
bool contains = false;
contains = this.itens2.Contains(this.itensList.SelectedItem);
if (!contains)
{
this.itens2.Add((Item)this.itensList.SelectedItem);
this.ItensToMaintaim = this.itens2;
}
if (this.itensList.SelectedIndex < this.itens1.Count - 1)
{
this.itensList.SelectedIndex++;
}
}
}
Once you set the DataSource, you can not add items to that collection.
"Items collection cannot be modified when the DataSource property is set."
But you can do it with some workarounds
1) Save to database and load it again with new values and bind it
OR
2) Get the exising data source of the Listbox and store it in a varaiable and add a new item (which is created from the selected item) and then re bind it again
Example ( the classes are specific to my need, you can customize based on your class structure)
//Take the existing
List<MailerKit> objExisting = (List<MailerKit>)comboBox1.DataSource;
//Add the new one
objExisting.Add(new MailerKit { KitName = comboBox1.SelectedText, ID = Convert.ToInt32(comboBox1.SelectedValue) });
//Rebind again
comboBox1.DataSource = objExisting;
comboBox1.DisplayMember = "KitName";
comboBox1.ValueMember = "ID";
You can create your custom events for adding and removing items in the lists. Handle these events in your control class where you have defined the comboboxes to add or remove their items.
Bind lists with datasource, as you did. Then remove and add items to lists, not to combobox itselves.
How do I create an Auto-complete ComboBox or TextBox that filters text based on a string?
For example: if I type an "a" in a TextBox, I only get to see all strings containing an "a".
If you mean show suggestions then it's a simple matter of changing a property when you have the TextBox selected in your IDE of choice:
The options shown in the picture allow you to change the rules for autocompleting text in the text box as well as the source for the suggestions. (Visual Studio 2010)
You can go to the following link to learn more about the TextBox control.
MSDN Text Box Control
Windows Forms's autocomplete implementation uses Shell's autocomplete object, which can only do a "BeginWith" match until Windows Vista.
If you can demand your users to upgrade to Windows Vista, you can use IAutoComplete2::SetOptions to specify ACO_NOPREFIXFILTERING. Otherwise I am afraid you need to write your own.
Here is how I auto-complete for a specific value in a comboDropDown box.
void comboBox_Leave(object sender, EventArgs e)
{
ComboBox cbo = (sender as ComboBox);
if (cbo.Text.Length > 0)
{
Int32 rowIndex = cbo.FindString(cbo.Text.Trim());
if (rowIndex != -1)
{
cbo.SelectedIndex = rowIndex;
}
else
{
cbo.SelectedIndex = -1;
}
}
else
{
cbo.SelectedIndex = -1;
}
}
This filters results based on user input. Optimizing for large lists, populating your own data and error handling left out for you to complete:
public partial class Form1 : Form
{
List<String> data;
ListView lst = new ListView();
TextBox txt = new TextBox();
public Form1()
{
InitializeComponent();
data = new List<string>("Lorem ipsum dolor sit amet consectetur adipiscing elit Suspendisse vel".Split());
}
private void Form1_Load(object sender, EventArgs e)
{
this.Controls.Add(txt);
lst.Top = 20;
txt.TextChanged += new EventHandler(txt_TextChanged);
lst.View = View.List;
this.Controls.Add(lst);
list_items("");
}
void txt_TextChanged(object sender, EventArgs e)
{
list_items(txt.Text);
}
void list_items(string filter)
{
lst.Items.Clear();
List<string> results = (from d in data where d.Contains(filter) select d).ToList();
foreach (string s in results)
{
lst.Items.Add(s);
}
}
}
To get the combobox to auto complete, set the AutoCompleteSource and AutoCompleteMode properties:
cbo.AutoCompleteSource = AutoCompleteSource.ListItems;
cbo.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
The ListItems source tells the combo to use it's items collection as the auto complete source.
I know this is an old topic but i tried so hard to find a solution for c# autocomplete filtering too and couldn't find any so i came up with my own simple and easy way so i'll just share it for those who may think it's useful and wanna use in their projects. It does not use the control's autocomplete features. What it does just simply get the text entered from the combobox, search it in the source then display only the matching ones from the source as the combobox' new source. I implemented it in the combobox' KeyUp event.
Let's say (actually this is what i do for almost all the cases when i want autocompleting) we have a globally assigned DataTable called dt_source to be the combobox' source and it has two columns: id(int) and name(string).
DataTable dt_source = new DataTable("table");
dt_source.Columns.Add("id", typeof(int));
dt_source.Columns.Add("name", typeof(string));
And this is what my KeyUp method looks like:
private void cmb_box_KeyUp(object sender, KeyEventArgs e)
{
string srch = cmb_box.Text;
string srch_str = "ABackCDeleteEFGHIJKLMNOPQRSpaceTUVWXYZD1D2D3D4D5D6D7D8D9D0";
if (srch_str.IndexOf(e.KeyCode.ToString()) >= 0)
{
cmb_box.DisplayMember = "name"; // we want name list in the datatable to be shown
cmb_box.ValueMember = "id"; // we want id field in the datatable to be the value
DataView dv_source = new DataView(dt_source); // make a DataView from DataTable so ...
dv_source.RowFilter = "name LIKE '%"+ srch +"%'"; // ... we can filter it
cmb_box.DataSource = dv_source; // append this DataView as a new source to the combobox
/* The 3 lines below is the tricky part. If you repopulate a combobox, the first
item in it will be automatically selected so let's unselect it*/
cmb_box.SelectedIndex = -1; // unselection
/* Again when a combobox repopulated the text region will be reset but we have the
srch variable to rewrite what's written before */
cmb_box.Text = srch;
/* And since we're rewriting the text region let's make the cursor appear at the
end of the text - assuming 100 chars is enough*/
cmb_box.Select(100,0);
cmb_box.DroppedDown = true; // Showing the dropdownlist
}
}
I think your best bet is to override the OnKeyDown(KeyEventArgs e) event, and use the value to filter the ComboBox.AutoCompleteCustomSource. Then as noted above, change the AutoCompleteSource to AutoCompleteSource.ListItems.