I am trying to add an autocomplete feature to a textbox, the results are coming from a database. They come in the format of
[001] Last, First Middle
Currently you must type [001]... to get the entries to show. So the problem is that I want it to complete even if I type the firstname first. So if an entry was
[001] Smith, John D
if I started typing John then this entry should show up in the results for the auto complete.
Currently the code looks something like
AutoCompleteStringCollection acsc = new AutoCompleteStringCollection();
txtBox1.AutoCompleteCustomSource = acsc;
txtBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
txtBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
....
if (results.Rows.Count > 0)
for (int i = 0; i < results.Rows.Count && i < 10; i++)
{
row = results.Rows[i];
acsc.Add(row["Details"].ToString());
}
}
results is a dataset containing the query results
The query is a simple search query using the like statement. The correct results are returned if we do not use the autocomplete and just toss the results into an array.
Any advice?
EDIT:
Here is the query that returns the results
SELECT Name from view_customers where Details LIKE '{0}'
With {0} being the placeholder for the searched string.
The existing AutoComplete functionality only supports searching by prefix. There doesn't seem to be any decent way to override the behavior.
Some people have implemented their own autocomplete functions by overriding the OnTextChanged event. That's probably your best bet.
For example, you can add a ListBox just below the TextBox and set its default visibility to false. Then you can use the OnTextChanged event of the TextBox and the SelectedIndexChanged event of the ListBox to display and select items.
This seems to work pretty well as a rudimentary example:
public Form1()
{
InitializeComponent();
acsc = new AutoCompleteStringCollection();
textBox1.AutoCompleteCustomSource = acsc;
textBox1.AutoCompleteMode = AutoCompleteMode.None;
textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
}
private void button1_Click(object sender, EventArgs e)
{
acsc.Add("[001] some kind of item");
acsc.Add("[002] some other item");
acsc.Add("[003] an orange");
acsc.Add("[004] i like pickles");
}
void textBox1_TextChanged(object sender, System.EventArgs e)
{
listBox1.Items.Clear();
if (textBox1.Text.Length == 0)
{
hideResults();
return;
}
foreach (String s in textBox1.AutoCompleteCustomSource)
{
if (s.Contains(textBox1.Text))
{
Console.WriteLine("Found text in: " + s);
listBox1.Items.Add(s);
listBox1.Visible = true;
}
}
}
void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
textBox1.Text = listBox1.Items[listBox1.SelectedIndex].ToString();
hideResults();
}
void listBox1_LostFocus(object sender, System.EventArgs e)
{
hideResults();
}
void hideResults()
{
listBox1.Visible = false;
}
There's a lot more you could do without too much effort: append text to the text box, capture additional keyboard commands, and so forth.
If you decide to use a query that is based on user input make sure you use SqlParameters to avoid SQL Injection attacks
SqlCommand sqlCommand = new SqlCommand();
sqlCommand.CommandText = "SELECT Name from view_customers where Details LIKE '%" + #SearchParam + "%'";
sqlCommand.Parameters.AddWithValue("#SearchParam", searchParam);
Here's an implementation that inherits the ComboBox control class, rather than replacing the whole combo-box with a new control. It displays its own drop-down when you type in the text box, but clicking to show the drop-list is handled as before (i.e. not with this code). As such you get that proper native control and look.
Please use it, modify it and edit the answer if you would like to improve it!
class ComboListMatcher : ComboBox, IMessageFilter
{
private Control ComboParentForm; // Or use type "Form"
private ListBox listBoxChild;
private int IgnoreTextChange;
private bool MsgFilterActive = false;
public ComboListMatcher()
{
// Set up all the events we need to handle
TextChanged += ComboListMatcher_TextChanged;
SelectionChangeCommitted += ComboListMatcher_SelectionChangeCommitted;
LostFocus += ComboListMatcher_LostFocus;
MouseDown += ComboListMatcher_MouseDown;
HandleDestroyed += ComboListMatcher_HandleDestroyed;
}
void ComboListMatcher_HandleDestroyed(object sender, EventArgs e)
{
if (MsgFilterActive)
Application.RemoveMessageFilter(this);
}
~ComboListMatcher()
{
}
private void ComboListMatcher_MouseDown(object sender, MouseEventArgs e)
{
HideTheList();
}
void ComboListMatcher_LostFocus(object sender, EventArgs e)
{
if (listBoxChild != null && !listBoxChild.Focused)
HideTheList();
}
void ComboListMatcher_SelectionChangeCommitted(object sender, EventArgs e)
{
IgnoreTextChange++;
}
void InitListControl()
{
if (listBoxChild == null)
{
// Find parent - or keep going up until you find the parent form
ComboParentForm = this.Parent;
if (ComboParentForm != null)
{
// Setup a messaage filter so we can listen to the keyboard
if (!MsgFilterActive)
{
Application.AddMessageFilter(this);
MsgFilterActive = true;
}
listBoxChild = listBoxChild = new ListBox();
listBoxChild.Visible = false;
listBoxChild.Click += listBox1_Click;
ComboParentForm.Controls.Add(listBoxChild);
ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
}
}
}
void ComboListMatcher_TextChanged(object sender, EventArgs e)
{
if (IgnoreTextChange > 0)
{
IgnoreTextChange = 0;
return;
}
InitListControl();
if (listBoxChild == null)
return;
string SearchText = this.Text;
listBoxChild.Items.Clear();
// Don't show the list when nothing has been typed
if (!string.IsNullOrEmpty(SearchText))
{
foreach (string Item in this.Items)
{
if (Item != null && Item.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
listBoxChild.Items.Add(Item);
}
}
if (listBoxChild.Items.Count > 0)
{
Point PutItHere = new Point(this.Left, this.Bottom);
Control TheControlToMove = this;
PutItHere = this.Parent.PointToScreen(PutItHere);
TheControlToMove = listBoxChild;
PutItHere = ComboParentForm.PointToClient(PutItHere);
TheControlToMove.Show();
TheControlToMove.Left = PutItHere.X;
TheControlToMove.Top = PutItHere.Y;
TheControlToMove.Width = this.Width;
int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
}
else
HideTheList();
}
/// <summary>
/// Copy the selection from the list-box into the combo box
/// </summary>
private void CopySelection()
{
if (listBoxChild.SelectedItem != null)
{
this.SelectedItem = listBoxChild.SelectedItem;
HideTheList();
this.SelectAll();
}
}
private void listBox1_Click(object sender, EventArgs e)
{
var ThisList = sender as ListBox;
if (ThisList != null)
{
// Copy selection to the combo box
CopySelection();
}
}
private void HideTheList()
{
if (listBoxChild != null)
listBoxChild.Hide();
}
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == 0x201) // Mouse click: WM_LBUTTONDOWN
{
var Pos = new Point((int)(m.LParam.ToInt32() & 0xFFFF), (int)(m.LParam.ToInt32() >> 16));
var Ctrl = Control.FromHandle(m.HWnd);
if (Ctrl != null)
{
// Convert the point into our parent control's coordinates ...
Pos = ComboParentForm.PointToClient(Ctrl.PointToScreen(Pos));
// ... because we need to hide the list if user clicks on something other than the list-box
if (ComboParentForm != null)
{
if (listBoxChild != null &&
(Pos.X < listBoxChild.Left || Pos.X > listBoxChild.Right || Pos.Y < listBoxChild.Top || Pos.Y > listBoxChild.Bottom))
{
this.HideTheList();
}
}
}
}
else if (m.Msg == 0x100) // WM_KEYDOWN
{
if (listBoxChild != null && listBoxChild.Visible)
{
switch (m.WParam.ToInt32())
{
case 0x1B: // Escape key
this.HideTheList();
return true;
case 0x26: // up key
case 0x28: // right key
// Change selection
int NewIx = listBoxChild.SelectedIndex + ((m.WParam.ToInt32() == 0x26) ? -1 : 1);
// Keep the index valid!
if (NewIx >= 0 && NewIx < listBoxChild.Items.Count)
listBoxChild.SelectedIndex = NewIx;
return true;
case 0x0D: // return (use the currently selected item)
CopySelection();
return true;
}
}
}
return false;
}
}
THIS WILL GIVE YOU THE AUTOCOMPLETE BEHAVIOR YOU ARE LOOKING FOR.
The attached example is a complete working form, Just needs your data source, and bound column names.
using System;
using System.Data;
using System.Windows.Forms;
public partial class frmTestAutocomplete : Form
{
private DataTable maoCompleteList; //the data table from your data source
private string msDisplayCol = "name"; //displayed text
private string msIDcol = "id"; //ID or primary key
public frmTestAutocomplete(DataTable aoCompleteList, string sDisplayCol, string sIDcol)
{
InitializeComponent();
maoCompleteList = aoCompleteList
maoCompleteList.CaseSensitive = false; //turn off case sensitivity for searching
msDisplayCol = sDisplayCol;
msIDcol = sIDcol;
}
private void frmTestAutocomplete_Load(object sender, EventArgs e)
{
testCombo.DisplayMember = msDisplayCol;
testCombo.ValueMember = msIDcol;
testCombo.DataSource = maoCompleteList;
testCombo.SelectedIndexChanged += testCombo_SelectedIndexChanged;
testCombo.KeyUp += testCombo_KeyUp;
}
private void testCombo_KeyUp(object sender, KeyEventArgs e)
{
//use keyUp event, as text changed traps too many other evengts.
ComboBox oBox = (ComboBox)sender;
string sBoxText = oBox.Text;
DataRow[] oFilteredRows = maoCompleteList.Select(MC_DISPLAY_COL + " Like '%" + sBoxText + "%'");
DataTable oFilteredDT = oFilteredRows.Length > 0
? oFilteredRows.CopyToDataTable()
: maoCompleteList;
//NOW THAT WE HAVE OUR FILTERED LIST, WE NEED TO RE-BIND IT WIHOUT CHANGING THE TEXT IN THE ComboBox.
//1).UNREGISTER THE SELECTED EVENT BEFORE RE-BINDING, b/c IT TRIGGERS ON BIND.
testCombo.SelectedIndexChanged -= testCombo_SelectedIndexChanged; //don't select on typing.
oBox.DataSource = oFilteredDT; //2).rebind to filtered list.
testCombo.SelectedIndexChanged += testCombo_SelectedIndexChanged;
//3).show the user the new filtered list.
oBox.DroppedDown = true; //do this before repainting the text, as it changes the dropdown text.
//4).binding data source erases text, so now we need to put the user's text back,
oBox.Text = sBoxText;
oBox.SelectionStart = sBoxText.Length; //5). need to put the user's cursor back where it was.
}
private void testCombo_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox oBox = (ComboBox)sender;
if (oBox.SelectedValue != null)
{
MessageBox.Show(string.Format(#"Item #{0} was selected.", oBox.SelectedValue));
}
}
}
//=====================================================================================================
// code from frmTestAutocomplete.Designer.cs
//=====================================================================================================
partial class frmTestAutocomplete
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.testCombo = new System.Windows.Forms.ComboBox();
this.SuspendLayout();
//
// testCombo
//
this.testCombo.FormattingEnabled = true;
this.testCombo.Location = new System.Drawing.Point(27, 51);
this.testCombo.Name = "testCombo";
this.testCombo.Size = new System.Drawing.Size(224, 21);
this.testCombo.TabIndex = 0;
//
// frmTestAutocomplete
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.testCombo);
this.Name = "frmTestAutocomplete";
this.Text = "frmTestAutocomplete";
this.Load += new System.EventHandler(this.frmTestAutocomplete_Load);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ComboBox testCombo;
}
If you're running that query (with {0} being replaced by the string entered), you might need:
SELECT Name from view_customers where Details LIKE '%{0}%'
LIKE still needs the % character... And yes, you should use parameters rather than trusting the user's input :)
Also, you seem to be returning the Name column, but querying on the Details column. So if someone types in "John Smith", if that's not in the Details column you won't get what you want back.
Two methods were successful in the autoComplete textBox control with SQL:
but you should do the following:
a- create new project
b- add Component class to project and delete component1.designer "according to the name you give to component class"
c- download "Download sample - 144.82 KB"
and open it and open AutoCompleteTextbox class from AutoCompleteTextbox.cs
d- select all as illustrated in the image and copy it to current component class
http://i.stack.imgur.com/oSqCa.png
e- Final - run project and stop to view new AutoCompleteTextbox in toolBox.
Now you can add the following two method that you can use SQL with them
1- in Form_Load
private void Form1_Load(object sender, EventArgs e)
{
SqlConnection cn = new SqlConnection(#"server=.;database=My_dataBase;integrated security=true");
SqlDataAdapter da = new SqlDataAdapter(#"SELECT [MyColumn] FROM [my_table]", cn);
DataTable dt = new DataTable();
da.Fill(dt);
List<string> myList = new List<string>();
foreach (DataRow row in dt.Rows)
{
myList.Add((string)row[0]);
}
autoCompleteTextbox1.AutoCompleteList = myList;
}
2- in TextChanged Event
private void autoCompleteTextbox_TextChanged(object sender, EventArgs e)
{
SqlConnection cn = new SqlConnection(#"server=.;database=My_dataBase;integrated security=true");
SqlDataAdapter da = new SqlDataAdapter(#"SELECT [MyColumn] FROM [my_table]", cn);
DataTable dt = new DataTable();
da.Fill(dt);
List<string> myList = new List<string>();
foreach (DataRow row in dt.Rows)
{
myList.Add((string)row[0]);
}
autoCompleteTextbox2.AutoCompleteList = myList;
}
Related
here I use the syncfusion framework, and it hasn't been too long since I've used this framework.
what I mean from my question is:
i have 3 columns. "Product_Name", "Cost_Price", "Sales_Price" and 50 rows of data in it. now i want to implement data filter programmatically with click event on sfButton1 based on value in column "Sales_Price" less than or equal to value in column "Cost_Price". and I only want to display data that has problem with Sales_Price, i.e. when Sales_Price Value <= Cost_Price value.
in the previous experiment I tried using telerik and this is in accordance with what I asked:
private void RAD_GridView_ShowData_CustomFiltering(object sender, GridViewCustomFilteringEventArgs e)
{
e.Visible = (int)e.Row.Cells["Sales_price"].Value <= (int)e.Row.Cells["Cost_price"].Value;
}
private void Btn_ShowFilter_Click(object sender, LinkLabelLinkClickedEventArgs e)
{
RAD_GridView_ShowData.EnableFiltering = true;
RAD_GridView_ShowData.EnableCustomFiltering = true;
RAD_GridView_ShowData.CustomFiltering += new GridViewCustomFilteringEventHandler(RAD_GridView_ShowData_CustomFiltering);
FilterDescriptor descriptor = new FilterDescriptor("Sales_price", FilterOperator.IsGreaterThanOrEqualTo, 0);
RAD_GridView_ShowData.FilterDescriptors.Add(descriptor);
}
but the telerik that I use is only a trial and will expire soon, so I use syncfusion which gives full control with the community license.
this code I got from the sync documentation: but it only filters data in "Stock" column based on value < 0 :
B_1_DGV_Stock.Columns["Stock"].FilterPredicates.Add(new FilterPredicate() { FilterType = FilterType.LessThan, FilterValue = "0" });
This requirement will be achievable by using SfDataGrid’s view filtering concept shown below,
public bool FilterRecords(object o)
{
var item = o as OrderInfo;
if (item != null)
{
if (item.UnitPrice <= item.Quantity)
return true;
}
return false;
}
private void button1_Click(object sender, EventArgs e)
{
sfDataGrid1.View.Filter = FilterRecords;
sfDataGrid1.View.RefreshFilter();
}
For more information related to Programmatic Filtering, please refer to the below user guide documentation link,
UG Link: https://help.syncfusion.com/windowsforms/datagrid/filtering#programmatic-filtering
Also, this is not supported for the DataTable. If you need to achieve the same with the Datatable collection too, it will be possible by using the ExpandoObject shown below,
ExpandoObject:
private DataTable dataTableCollection;
private ObservableCollection<dynamic> dynamicCollection;
public Form1()
{
InitializeComponent();
this.WindowState = FormWindowState.Maximized;
//Gets the data for DataTable object.
dataTableCollection = GetGridData();
//Convert DataTable collection as Dyanamic collection.
dynamicCollection = new ObservableCollection<dynamic>();
foreach (System.Data.DataRow row in dataTableCollection.Rows)
{
dynamic dyn = new ExpandoObject();
dynamicCollection.Add(dyn);
foreach (DataColumn column in dataTableCollection.Columns)
{
var dic = (IDictionary<string, object>)dyn;
dic[column.ColumnName] = row[column];
}
}
DynamicOrders = dynamicCollection;
sfDataGrid1.AutoGenerateColumns = true;
sfDataGrid1.DataSource = DynamicOrders;
}
private ObservableCollection<dynamic> _dynamicOrders;
/// <summary>
/// Gets or sets the dynamic orders.
/// </summary>
/// <value>The dynamic orders.</value>
public ObservableCollection<dynamic> DynamicOrders
{
get
{
return _dynamicOrders;
}
set
{
_dynamicOrders = value;
}
}
public DataTable DataTableCollection
{
get { return dataTableCollection; }
set { dataTableCollection = value; }
}
Filtering concept:
public bool FilterRecords(object o)
{
var emplyeeAge = ((ExpandoObject)o).FirstOrDefault(z => z.Key == "EmployeeAge").Value;
var memmbersCount = ((ExpandoObject)o).FirstOrDefault(z => z.Key == "MemmbersCount").Value;
if (emplyeeAge != null && memmbersCount != null)
{
if (double.Parse(emplyeeAge.ToString()) < (double.Parse(memmbersCount.ToString())))
return true;
}
return false;
}
private void FilterClick(object sender, System.EventArgs e)
{
sfDataGrid1.View.Filter = FilterRecords;
sfDataGrid1.View.RefreshFilter();
}
enter image description here
namespace Implementer
{
public partial class MainForm : Form
{
#region intit and globals
public MainForm()
{
InitializeComponent();
DevExpress.XtraGrid.Views.Grid.GridView gridView = new DevExpress.XtraGrid.Views.Grid.GridView();
var transactions = new ObservableCollection<Item>();
for (int i = 0; i <= 100; i++)
{
transactions.Add(new Item { Content = "Item " + i });
}
grdTransactions.AllowDrop = true;
grdTransactions.DataSource = transactions;
dgmWf.AddingNewItem += (s, e) => transactions.Remove(e.Item.Tag as Item);
}
Point mouseDownLocation;
GridHitInfo gridHitInfo;
#endregion
#region events
public void Mainform_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'vA_ERP4_ADMINDataSet.SYSTEM_MODULES' table. You can move, or remove it, as needed.
//Project initiation
//Fill drop down for project list
cmbProject.SelectedIndex = 0;
DataAccess dataAccess = new DataAccess(GlobalFunctions.GetConnectionString());
DataTable dtResult = dataAccess.ExecuteQueryDataSet("select MODULE_CODE, MODULE_DESC from SYSTEM_MODULES where module_is_active=1").Tables[0];
cmbProject.DisplayMember = "MODULE_DESC";
cmbProject.ValueMember = "MODULE_CODE";
cmbProject.DataSource = dtResult;
}
private void cmbProject_SelectedIndexChanged(object sender, EventArgs e)
{
lblCurrentProject.Text = cmbProject.Text;
if (cmbProject.Text != null)
{
DataAccess dataAccess = new DataAccess(GlobalFunctions.GetConnectionString());
DataTable dtTransactions = dataAccess.ExecuteQueryDataSet("select sys_trans_id, sys_trans_desc1 from WF_SYSTEM_TRANS where MODULE_CODE= '" + cmbProject.Text + "'").Tables[0];
grdTransactions.DataSource = dtTransactions;
}
}
private void btnSalesInvoice_Click(object sender, EventArgs e)
{
int sysTransId = 1001;
FillTransactionDetails(sysTransId);
}
#endregion
#region drag drop
private void grdTransactions_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && CanStartDragDrop(e.Location))
{
StartDragDrop();
}
}
private void grdTransactions_MouseDown(object sender, MouseEventArgs e)
{
gridHitInfo = grdVTransactions.CalcHitInfo(e.Location);
mouseDownLocation = e.Location;
}
private void grdTransactions_MouseLeave(object sender, EventArgs e)
{
if (gridHitInfo != null)
gridHitInfo.View.ResetCursor();
gridHitInfo = null;
}
private bool CanStartDragDrop(Point location)
{
return gridHitInfo.InDataRow && (Math.Abs(location.X - mouseDownLocation.X) > 2 || Math.Abs(location.Y - mouseDownLocation.Y) > 2);
}
public void StartDragDrop()
{
var draggedRow = gridHitInfo.View.GetRow(gridHitInfo.RowHandle) as Item;
var tool = new FactoryItemTool(" ", () => " ", diagram => new DiagramShape(BasicShapes.Rectangle) { Content = draggedRow.Content, Tag = draggedRow }, new System.Windows.Size(150, 100), false);
dgmWf.Commands.Execute(DiagramCommandsBase.StartDragToolCommand, tool, null);
}
#endregion
#region function
private void FillTransactionDetails(int systemTransactionId)
{
//Fill document
//Fill steps
DataAccess dataAccess = new DataAccess(GlobalFunctions.GetConnectionString());
DataTable transactionDetails = dataAccess.ExecuteQueryDataSet("SELECT DOC_TYPE_DESC1 FROM WF_SYSTEM_TRANS_DT WHERE SYS_TRANS_ID=1001 and MODULE_CODE= '" + cmbProject.Text + "'").Tables[0];
transactionDetails.Rows.Add();
grdDocuments.DataSource = transactionDetails;
grdDocuments.Columns["Details"].DisplayIndex = 2;
grdDocuments.Columns["Delete"].DisplayIndex = 2;
DataTable transactionSteps = dataAccess.ExecuteQueryDataSet("select WF_STEP_DESC1 from WF_STEPS where wf_id= 10101 and MODULE_CODE= '" + cmbProject.Text + "'").Tables[0];
transactionSteps.Rows.Add();
grdSteps.DataSource = transactionSteps;
}
#endregion
}
public class Item
{
public string Content { get; set; }
}
}
I don't really know where is the mistake and have been looking at it for the past few days and searching for an answer but no luck so I'd be so happy if you could help me out. It was working without the data fetching. but after calling the data it doesn't work. drag it from the grid view and when it reaches the diagram control it would turn into a rectangle with a tag property of it's ID.. With regards to the connection string.. I created a global function to just call it on the main form.
The problem I'm having is where I can successfully use the RoomID of the Room object I've created, but not the panel. Here is the function where I set the label to the name of the room. (This is my Form1.cs)
public partial class Form1 : Form
{
Room ThisRoom = new Room();
public Form1()
{
InitializeComponent();
this.Text = "Aquinas College Master Controller";
}
private void roomDesignerToolStripMenuItem_Click(object sender, EventArgs e)
{
new RoomDesigner().Show();
}
private void button1_Click(object sender, EventArgs e)
{
roomsToolStripMenuItem.DropDownItems.Clear();
foreach (var Room in Global.Aquinas.Aquinas)
{
ToolStripMenuItem NewItem = new ToolStripMenuItem(Room.RoomID);
NewItem.Name = Room.RoomID;
NewItem.Click += new EventHandler(ItemClick);
roomsToolStripMenuItem.DropDownItems.Add(NewItem);
}
}
void ItemClick(object sender, EventArgs e)
{
ToolStripItem item = (ToolStripItem)sender;
label2.Text = item.Name;
foreach (var Room in Global.Aquinas.Aquinas)
{
if (Room.RoomID == item.Name)
{
ThisRoom = Room;
break;
}
}
Panel RoomPanel = ThisRoom.Panel;
RoomPanel.Size = new Size(607, 304);
RoomPanel.Location = new Point(144, 27);
RoomPanel.BackColor = Color.White;
this.Controls.Add(RoomPanel);
}
}
This is some of my code for the room object. (This is in Room.cs)
public class Room
{
private List<CtrlComputer> _Computers;
public List<CtrlComputer> Computers
{
get { return _Computers; }
set { _Computers = value; }
}
private string _RoomID;
public Room()
{
_Computers=new List<CtrlComputer>();
}
public string RoomID
{
get
{
return _RoomID;
}
set
{
_RoomID = value;
}
}
public Panel Panel
{
get
{
return _Panel;
}
set
{
_Panel = value;
}
}
private Panel _Panel;
}
And here is where I register the panel which I have just put my designs on, to a new room. (This is some of my RoomDesigner.cs)
public partial class RoomDesigner : Form
{
Room NewRoom = new Room();
Panel RoomDesignerPanel = new Panel();
public RoomDesigner()
{
InitializeComponent();
RoomDesignerPanel.Size = new Size(607, 304);
RoomDesignerPanel.Location = new Point(144, 27);
RoomDesignerPanel.BackColor = Color.White;
this.Controls.Add(RoomDesignerPanel);
textBox1.ForeColor = SystemColors.GrayText;
textBox1.Text = "Enter Computer ID Here";
this.textBox1.Leave += new System.EventHandler(this.textBox1_Leave);
this.textBox1.Enter += new System.EventHandler(this.textBox1_Enter);
textBox2.ForeColor = SystemColors.GrayText;
textBox2.Text = "Enter Room ID Here";
this.textBox2.Leave += new System.EventHandler(this.textBox2_Leave);
this.textBox2.Enter += new System.EventHandler(this.textBox2_Enter);
}
private void textBox1_Leave(object sender, EventArgs e)
{
if (textBox1.Text.Length == 0)
{
textBox1.Text = "Enter Computer ID Here";
textBox1.ForeColor = SystemColors.GrayText;
}
}
private void textBox1_Enter(object sender, EventArgs e)
{
if (textBox1.Text == "Enter Computer ID Here")
{
textBox1.Text = "";
textBox1.ForeColor = SystemColors.WindowText;
}
}
private void textBox2_Leave(object sender, EventArgs e)
{
if (textBox2.Text.Length == 0)
{
textBox2.Text = "Enter Room ID Here";
textBox2.ForeColor = SystemColors.GrayText;
}
}
private void textBox2_Enter(object sender, EventArgs e)
{
if (textBox2.Text == "Enter Room ID Here")
{
textBox2.Text = "";
textBox2.ForeColor = SystemColors.WindowText;
}
}
private void button1_Click(object sender, EventArgs e)
{
CtrlComputer NewComputer = new CtrlComputer();
NewComputer.ComputerID = textBox1.Text;
NewComputer.Text = NewComputer.ComputerID;
NewComputer.Parent = RoomDesignerPanel;
RoomDesignerPanel.Controls.Add(NewComputer);
NewRoom.Panel = RoomDesignerPanel;
NewRoom.Add(NewComputer);
}
When I try to reload that panel, the RoomID of that object is returned fine, yet the panel is not. Any ideas?
Edit: Sorry for the lack of clarity in my post, first posts aren't always easy.
The panel is created in the designer, but I tried private Panel Panel1 = new Panel(); with no luck. My current problem is that in Form1.cs, the program will throw "Cannot access a disposed object", but I followed the guide here https://msdn.microsoft.com/en-us/library/82785s1h(v=vs.110).aspx .
What you are looking to do is swap between a collection of Panel objects (kind of like your own version of TabPages). To do this you need to create each of the Panel objects (You do this in RoomDesigner, which is fine, but there is a catch: see * below). With that collection in hand, when you want to show it on the form, you need to remove the panel1 the designer code created (Controls.Remove(panel1)) and insert your new one (Controls.Add(RoomPanel)).
Since Controls utilize system resources, make sure to Dispose any control you will no longer use. Here is an example method:
//Initial case where no room has been displayed yet and panel1 is still valid
private Panel CurrentPanel = null;
public void SwapPanel(Panel p)
{
//If no panel has been placed yet, get rid of the default one
if (panel1 != null)
{
//we should never need the designer panel again, so dispose it
panel1.Dispose();
this.Controls.Remove(panel1);
}
else
{
//we may return to this panel later, so don't dispose it
this.Controls.Remove(CurrentPanel);
}
CurrentPanel = p;
this.Controls.Add(p);
}
When you finally close out and no longer need your Panels stored in your Room collection, make sure to Dispose them. (However, if your whole program is exiting at that point then it doesn't matter, but do it anyway as good practice)
*When you close any RoomDesigner form (or any form), all controls it contains in 'Controls' will be disposed, even if you are using them elsewhere. To prevent this, remove the controls you want to preserve from the RoomDesigner Controls collection before it is closed.
I have setup a ComboBoxColumn for my DataGridView and set its selectable values from an enumeration. It mostly works as I would like with the following exception.
Whenever I click the dropdown arrow and then select one of the enum values, it remains in sort of a "intermediate" state where the CellValueChanged event isn't triggered. I need to focus on another cell or another control for the event to fire.
I also have an event handler for the DataGridView's Leaving event which "validates" the contents by making sure that no cell is empty.
So, if I create a row and fill all the cells and come to the (currently blank) ComboBox column, change it to a value, and then click a Run button; my error dialog pops up because the ComboBox selection wasn't "saved".
How can I get around this? Is there a way that after I select a value from the drop down it automatically "sets" the value?
Thanks!
You should use CurrentCellDirtyStateChanged event and force a commit edit on the grid:
private void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
Hope it helps!
I would extend Moop's answer by checking the cell type instead of the column type.
dataGridView1.CurrentCellDirtyStateChanged += dataGridView1_CurrentCellDirtyStateChanged;
void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (CurrentCell is DataGridViewComboBoxCell)
{
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
dataGridView1.EndEdit();
}
}
I would extend ionden's answer by checking if the DataGridViewColumn is the type of DataGridViewComboBoxColumn before forcing the CommitEdit. This will prevent other DataGridViewColumn objects from committing too early.
dataGridView1.CurrentCellDirtyStateChanged += dataGridView1_CurrentCellDirtyStateChanged;
void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
DataGridViewColumn col = dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex];
if (col is DataGridViewComboBoxColumn)
{
dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
The CurrentCellDirtyStateChanged event fixed mouse interaction for this issue, but it breaks keyboard interaction - using F4 then up/down arrow, every arrow click results in a dirty state change and commits the edit. The solution I found, was to grab the "DataGridViewComboBoxEditingControl" when it's created, and attach a DropDownClosed event to it. This works for keyboard and mouse interaction. In this example, we extended DataGridView so every instance would inherit this functionality:
protected override void OnEditingControlShowing(DataGridViewEditingControlShowingEventArgs e)
{
DataGridViewComboBoxEditingControl control = e.Control as DataGridViewComboBoxEditingControl;
if (control != null)
{
control.DropDownClosed -= ComboBoxDropDownClosedEvent;
control.DropDownClosed += ComboBoxDropDownClosedEvent;
}
base.OnEditingControlShowing(e);
}
void ComboBoxDropDownClosedEvent(object sender, EventArgs e)
{
DataGridViewComboBoxCell cell = CurrentCell as DataGridViewComboBoxCell;
if ((cell != null) && cell.IsInEditMode)
{
CommitEdit(DataGridViewDataErrorContexts.Commit);
EndEdit();
}
}
In some cases, the value won't stick until the focus has left the row entirely. In that case, the only way to force the current edit to end is to end it on the whole binding context:
mGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
mGridView.BindingContext[mGridView.DataSource].EndCurrentEdit(); // <<===
I found this tip here.
I spend like two hours searching for an error because I did not notice that the cell value does not get saved if it´s not defocused, or better to say I just noticed that the cell is not defocused because the combobox whited out while saving(btn event).
Not only that, the EditOnEnter-Mode prevails that most other methods shown above work. The reason to use EditOnEnter is that when you use a DataGridViewComboBoxColumn, you have to click two times to open the dropdown if you do not set EditMode to EditOnEnter.
this.dataGridView.EditMode = DataGridViewEditMode.EditOnKeystrokeOrF2;
this.dataGridView.EndEdit();
this.dataGridView.EditMode = DataGridViewEditMode.EditOnEnter;
I hope this helps. It cost me around two hours wondering why the value in the object is not the same then shown on the GUI.
I am adding my answer as a follow-up to the discussion that has already occurred. I was trying to build a DataGridView that had different comboboxes per row. They also had to be responsive to a single click. And, when the selection was made, another cell in the row needed to be changed according to the combobox selection. The change needed to happen as soon as the selection was made. My main problem, like the OP's, was the change wouldn't happen until the combobox lost focus.
So, here is a full working minimal example of such a DataGridView. I had to bring it down to a minimum because getting all my requirements to work at the same time was tricky. Several SO posts went into making this, and I will update my post with references later. But for now, here goes...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace TestDGV
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Panel panel2;
private DataGridView TestGrid;
private void InitializeComponent()
{
this.panel2 = new System.Windows.Forms.Panel();
this.SuspendLayout();
//
// panel2
//
this.panel2.Dock = DockStyle.Fill;
this.panel2.Name = "panel2";
this.panel2.TabIndex = 1;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(661, 407);
this.Controls.Add(this.panel2);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
private void Form1_Load(object sender, EventArgs e)
{
//basic grid properties
TestGrid = new DataGridView();
TestGrid.Dock = DockStyle.Fill;
TestGrid.AutoGenerateColumns = false;
TestGrid.Name = "TestGrid";
TestGrid.ReadOnly = false;
TestGrid.EditMode = DataGridViewEditMode.EditOnEnter;
//Event handlers
TestGrid.DataBindingComplete += TestGrid_DataBindingComplete;
TestGrid.CurrentCellDirtyStateChanged += TestGrid_CurrentCellDirtyStateChanged;
TestGrid.CellValueChanged += TestGrid_CellValueChanged;
//columns
var textCol = new DataGridViewTextBoxColumn();
textCol.HeaderText = "Text";
textCol.Name = "Text";
textCol.DataPropertyName = "Text";
textCol.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
TestGrid.Columns.Add(textCol);
var comboCol = new DataGridViewComboBoxColumn();
comboCol.HeaderText = "ComboBox";
comboCol.Name = "ComboBox";
comboCol.AutoComplete = true;
comboCol.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
TestGrid.Columns.Add(comboCol);
var resultCol = new DataGridViewTextBoxColumn();
resultCol.HeaderText = "Result";
resultCol.Name = "Result";
resultCol.DataPropertyName = "Result";
resultCol.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
TestGrid.Columns.Add(resultCol);
//Bind the data
Datum.TestLoad();
TestGrid.DataSource = Datum.Data;
panel2.Controls.Add(TestGrid);
}
void TestGrid_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex < 0 || e.ColumnIndex < 0)
return;
var row = TestGrid.Rows[e.RowIndex];
var cell = row.Cells[e.ColumnIndex];
if (cell is DataGridViewComboBoxCell)
{
var val = cell.Value as string;
var datum = row.DataBoundItem as Datum;
datum.Current = val;
row.Cells["Result"].Value = datum.Result;
TestGrid.InvalidateRow(e.RowIndex);
}
}
void TestGrid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if(TestGrid.CurrentCell is DataGridViewComboBoxCell)
{
TestGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
TestGrid.EndEdit();
}
}
void TestGrid_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
foreach (DataGridViewRow row in TestGrid.Rows)
{
var datum = row.DataBoundItem as Datum;
if (datum == null)
return;
var cell = row.Cells["ComboBox"] as DataGridViewComboBoxCell;
if (cell.DataSource == null)
{
cell.DisplayMember = "KeyDisplayValue";
cell.ValueMember = "KeyValue";
cell.DataSource = (row.DataBoundItem as Datum).Combo;
cell.Value = (row.DataBoundItem as Datum).Current;
}
}
TestGrid.DataBindingComplete -= TestGrid_DataBindingComplete;
}
public class Datum
{
public static void TestLoad()
{
var t1 = new Triplet[] {
new Triplet("1", "World", "Everyone" ),
new Triplet("2", "Charlie", "Friend of Algernon" ),
new Triplet("3", "Lester", "Phenomenal programmer" ),
};
var t2 = new Triplet[] {
new Triplet("1", "World", "Everyone" ),
new Triplet("4", "Mary", "Wife of George Bailey" ),
new Triplet("3", "Lester", "Phenomenal programmer" ),
};
Data.Add(new Datum("hello, ", t1.ToList()));
Data.Add(new Datum("g'bye, ", t2.ToList()));
}
public static List<Datum> Data = new List<Datum>();
public Datum(string text, List<Triplet> combo)
{
this._text = text;
this._combo = combo.ToDictionary<Triplet,string>(o => o.KeyValue);
this.Current = combo[0].KeyValue;
}
private string _text;
public string Text
{
get
{
return _text;
}
}
private Dictionary<string, Triplet> _combo;
public List<Triplet> Combo
{
get
{
return _combo.Values.ToList();
}
}
private string _result;
public string Result
{
get
{
return _result;
}
}
private string _current;
public string Current
{
get
{
return _current;
}
set
{
if (value != null && _combo.ContainsKey(value))
{
_current = value;
_result = _combo[value].Description;
}
}
}
}
public class Triplet
{
public string KeyValue { get; set; }
public string KeyDisplayValue { get; set; }
public string Description { get; set; }
public Triplet(string keyValue, string keyDisplayValue, string description)
{
KeyValue = keyValue;
KeyDisplayValue = keyDisplayValue;
Description = description;
}
}
}
}
Thanks to Droj for the tip about EndCurrentEdit, which I needed to make it work for me.
This is what I ended up doing to instantly commit DataGridViewComboBoxColumns and DataGridViewCheckBoxColumns:
private void dataGridViewEnumerable_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
var dataGridView = sender as DataGridView;
if (dataGridView == null || dataGridView.CurrentCell == null)
return;
var isComboBox = dataGridView.CurrentCell is DataGridViewComboBoxCell;
if ((isComboBox || dataGridView.CurrentCell is DataGridViewCheckBoxCell)
&& dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit)
&& isComboBox && dataGridView.EndEdit())
dataGridView.BindingContext[dataGridView.DataSource].EndCurrentEdit();
}
Here's how I solved the issue
Private Sub dgvEcheancier_CurrentCellDirtyStateChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles dgvEcheancier.CurrentCellDirtyStateChanged
nbreClick += 1
With dgvEcheancier
Select Case .CurrentCell.ColumnIndex
Case 9
Dim col As DataGridViewComboBoxColumn = .Columns(9)
If TypeOf (col) Is DataGridViewComboBoxColumn Then
dgvEcheancier.CommitEdit(DataGridViewDataErrorContexts.Commit)
If nbreClick = 2 Then
MessageBox.Show("y" & "val=" & .CurrentCell.Value)
nbreClick = 0
End If
End If
End Select
End With
void dataGridView1_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
dataGridView1.BeginEdit(true);
ComboBox cmbMiCtrl=(ComboBox)dataGridView1.EditingControl;
string Valor= cmbMiCtrl.Text;
dataGridView1.EndEdit();
}
One problem that I saw : It won't work if you choose :
GridView.EditMode = System.Windows.Forms.DataGridViewEditMode.EditOnEnter;
You should use CellValueChanged which fires the change event on grid and inside the event you should commit changes and leave the control in order to save the item after it is selected.
private void FilterdataGrid_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
FilterdataGrid.CommitEdit(DataGridViewDataErrorContexts.Commit);
FilterdataGrid.EndEdit(DataGridViewDataErrorContexts.LeaveControl);
}
Hope it helps!
I have a list of words. The list contains about 100-200 text strings (it's names of metro stations actually).
I want to make an auto-complete textbox. For an example, user press 'N' letter, then an (ending of) appropriate option appear (only one option). The ending must be selected.
How to do that?
PS1: I guess, there is a textbox control with a Property something like this:
List<string> AppropriateOptions{/* ... */}
PS2: sorry for my english. If you didn't understand -> ask me and I will try to explain!
Just in case #leniel's link goes down, here's some code that does the trick:
AutoCompleteStringCollection allowedTypes = new AutoCompleteStringCollection();
allowedTypes.AddRange(yourArrayOfSuggestions);
txtType.AutoCompleteCustomSource = allowedTypes;
txtType.AutoCompleteMode = AutoCompleteMode.Suggest;
txtType.AutoCompleteSource = AutoCompleteSource.CustomSource;
Use a ComboBox instead of a TextBox. The following example will autocomplete, matching any piece of the text, not just the starting letters.
This should be a complete form, just add your own data source, and data source column names. :-)
using System;
using System.Data;
using System.Windows.Forms;
public partial class frmTestAutocomplete : Form
{
private DataTable maoCompleteList;
private const string MC_DISPLAY_COL = "name";
private const string MC_ID_COL = "id";
public frmTestAutocomplete()
{
InitializeComponent();
}
private void frmTestAutocomplete_Load(object sender, EventArgs e)
{
maoCompleteList = oData.PurificationRuns;
maoCompleteList.CaseSensitive = false; //turn off case sensitivity for searching
testCombo.DisplayMember = MC_DISPLAY_COL;
testCombo.ValueMember = MC_ID_COL;
testCombo.DataSource = GetDataTableFromDatabase();
testCombo.SelectedIndexChanged += testCombo_SelectedIndexChanged;
testCombo.KeyUp += testCombo_KeyUp;
}
private void testCombo_KeyUp(object sender, KeyEventArgs e)
{
//use keyUp event, as text changed traps too many other evengts.
ComboBox oBox = (ComboBox)sender;
string sBoxText = oBox.Text;
DataRow[] oFilteredRows = maoCompleteList.Select(MC_DISPLAY_COL + " Like '%" + sBoxText + "%'");
DataTable oFilteredDT = oFilteredRows.Length > 0
? oFilteredRows.CopyToDataTable()
: maoCompleteList;
//NOW THAT WE HAVE OUR FILTERED LIST, WE NEED TO RE-BIND IT WIHOUT CHANGING THE TEXT IN THE ComboBox.
//1).UNREGISTER THE SELECTED EVENT BEFORE RE-BINDING, b/c IT TRIGGERS ON BIND.
testCombo.SelectedIndexChanged -= testCombo_SelectedIndexChanged; //don't select on typing.
oBox.DataSource = oFilteredDT; //2).rebind to filtered list.
testCombo.SelectedIndexChanged += testCombo_SelectedIndexChanged;
//3).show the user the new filtered list.
oBox.DroppedDown = true; //this will overwrite the text in the ComboBox, so 4&5 put it back.
//4).binding data source erases text, so now we need to put the user's text back,
oBox.Text = sBoxText;
oBox.SelectionStart = sBoxText.Length; //5). need to put the user's cursor back where it was.
}
private void testCombo_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox oBox = (ComboBox)sender;
if (oBox.SelectedValue != null)
{
MessageBox.Show(string.Format(#"Item #{0} was selected.", oBox.SelectedValue));
}
}
}
//=====================================================================================================
// code from frmTestAutocomplete.Designer.cs
//=====================================================================================================
partial class frmTestAutocomplete
{
/// <summary>
/// Required designer variable.
/// </summary>
private readonly System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.testCombo = new System.Windows.Forms.ComboBox();
this.SuspendLayout();
//
// testCombo
//
this.testCombo.FormattingEnabled = true;
this.testCombo.Location = new System.Drawing.Point(27, 51);
this.testCombo.Name = "testCombo";
this.testCombo.Size = new System.Drawing.Size(224, 21);
this.testCombo.TabIndex = 0;
//
// frmTestAutocomplete
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.Add(this.testCombo);
this.Name = "frmTestAutocomplete";
this.Text = "frmTestAutocomplete";
this.Load += new System.EventHandler(this.frmTestAutocomplete_Load);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.ComboBox testCombo;
}
The answer link by Leniel was in vb.net, thanks Joel for your entry. Supplying my code to make it more explicit:
private void InitializeTextBox()
{
AutoCompleteStringCollection allowedStatorTypes = new AutoCompleteStringCollection();
var allstatortypes = StatorTypeDAL.LoadList<List<StatorType>>().OrderBy(x => x.Name).Select(x => x.Name).Distinct().ToList();
if (allstatortypes != null && allstatortypes.Count > 0)
{
foreach (string item in allstatortypes)
{
allowedStatorTypes.Add(item);
}
}
txtStatorTypes.AutoCompleteMode = AutoCompleteMode.Suggest;
txtStatorTypes.AutoCompleteSource = AutoCompleteSource.CustomSource;
txtStatorTypes.AutoCompleteCustomSource = allowedStatorTypes;
}
Use combo box, sets its datasource or give hard coded entries but set the following properties:
AutoCompleteMode = Suggest;
AutoCompleteSource = ListItems;
You want to set the TextBox.AutoCompleteSource to CustomSource and then add all of your strings to its AutoCompleteCustomSource property, which is a StringCollection. Then you should be good to go.
I want to add that the standard autocomplete for TextBox does only work from the beginning of your strings, so if you hit N only strings starting with N will be found. If you want to have something better, you have to use some different control or implement the behavior for yourself (i.e. react on TextChanged Event with some timer to delay execution, than filter your tokenlist searching with IndexOf(inputString) and then set your AutoCompleteSource to the filtered list.