I have a GridLookUpEdit controller and ToolTipController and want to show tooltips for GridLookUpEdit's rows on FocusedRowChanged event.
But i cant find any examples.
Im already tried:
toolTipController1.SetToolTip(MyGridLookUpEdit, "Test");
But tooltip not shown.
private void toolTipController1_GetActiveObjectInfo(object sender, ToolTipControllerGetActiveObjectInfoEventArgs e)
{
ToolTipControlInfo info = null;
GridHitInfo hi = view.CalcHitInfo(e.ControlMousePosition);
object o = hi.HitTest.ToString() + hi.RowHandle.ToString();
string text = "Row " + hi.RowHandle.ToString();
info = new ToolTipControlInfo(o, text);
if (info != null)
e.Info = info;
}
Same result.
What can be wrong?
You need to attach your ToolTipController to underlying GridControl of your GridLookUpEdit:
gridLookUpEdit1.Properties.View.GridControl.ToolTipController = toolTipController1;
Then you can use ToolTipController.GetActiveObjectInfo event to show the tooltip. To get the focused value you can use ColumnView.GetFocusedRowCellValue method or GridView.GetFocusedValue method.
Here is example:
private void toolTipController1_GetActiveObjectInfo(object sender, ToolTipControllerGetActiveObjectInfoEventArgs e)
{
var gridControl = gridLookUpEdit1.Properties.View.GridControl;
if (e.SelectedControl == gridControl)
{
var view = gridControl.GetViewAt(e.ControlMousePosition) as GridView;
if (view != null)
{
object focusedValue = view.GetFocusedRowCellValue(view.Columns[0]);
if (focusedValue != null)
e.Info = new ToolTipControlInfo(view.FocusedRowHandle, focusedValue.ToString());
}
}
}
First make sure you've attached the controller
MyGridLookUpEdit.ToolTipController = toolTipController1;
Then try this
private void toolTipController1_GetActiveObjectInfo(object sender, ToolTipControllerGetActiveObjectInfoEventArgs e)
{
GridHitInfo hi = view.CalcHitInfo(e.ControlMousePosition);
if (hi.InRowCell)
{
string text = "Row " + hi.RowHandle.ToString();
e.Info = new ToolTipControlInfo(hi.RowHandle, text);
}
}
Related
I'm working on a Windows Form Application. Textbox index can be saved and shown as in ListBox with this code:
private List<FunctionData> funcParamList = new List<FunctionData>();
...
private void addFuncButton_Click(object sender, EventArgs e)
{
FunctionData funcParams = new FunctionData();
funcParams.blabla1name = blabla1.Text;
funcParams.blabla2name = blabla2.Text;
...
if (funcParams.isValid())
{
funcParamList.Add(funcParams);
functionListBox.Items.Add(functionNameBox.Text);
}
Also I collect objects to TextBox again to edit (by clicking ListBox item) with the following code :
private void getParams(FunctionData data)
{
blabla1.Text = data.blabla1name;
blabla2.Text = data.blabla2name;
functionNameBox.Text = data.functionName;
return;
}
private void functionListBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (functionListBox.SelectedItem == null) { return; }
foreach (var obj in funcParamList)
{
if (obj.functionName == functionListBox.SelectedItem.ToString())
{
getParams(obj);
}
}
}
And save them to file as JSON with:
private void saveFileButton_Click(object sender, EventArgs e)
{
fileName = fileNameBox.Text;
string jsonFunc = JsonConvert.SerializeObject(funcParamList);
System.IO.File.WriteAllText(#"<blablapath>\" + fileName + ".txt", jsonFunc);
}
There's 'functionName' object in JSON file that I can use it for showing on ListBox.
My question is: How can I load this file buy Native Load/Open File Dialog and show the objects in ListBox and can edit them again?
And here how I've tried to make it with the following code, but it doesn't work:
private void loadFileButton_Click(object sender, EventArgs e)
{
OpenFileDialog loadFileDialog = new OpenFileDialog();
...
if (loadFileDialog.ShowDialog() == DialogResult.OK)
{
string jsonFileName = loadFileDialog.FileName;
string jsonFile = File.ReadAllText(jsonFileName);
dynamic loadedFile = JsonConvert.DeserializeObject(jsonFile);
//if (functionListBox.SelectedItem == null) { return; }
foreach (var obj in loadedFile)
{
if (obj.functionName != null)
{
functionListBox.Items.Add(obj.functionName);
getParams(obj); // I get exception here
funcParamList.Add(loadedFile);
functionListBox.Refresh();
}
}
}
I've solved the problem by casting 'DeserializeObject' as List and it's done. Here the changes:
...
var loadedFile = JsonConvert.DeserializeObject<List<FunctionData>>(jsonFile);
I am using an XtraGridView control in my winform. Now I added a RepositoryItemHyperLinkEdit to it. But I want to show/hide each link according to the row data.
How can I achieve this?
Thanks for any help..
I tried the next code but it did not work, the cell did not be empty.
("Show link" part is ok, but String.Empty does not work)
private void xgvGrid_CustomColumnDisplayText(object sender, DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventArgs e)
{
if (e.Column == gcControlField)
{
if (xgvGrid.GetFocusedRowCellValue("ControlField") != null)
{
if (xgvGrid.GetFocusedRowCellValue("ControlField").ToString() == "LINK")
e.DisplayText = "Show link";
else
e.DisplayText = string.Empty;
}
}
}
You can add your checking in the event GridView.CustomColumnDisplayText.
e.g. Each row is bind to a Person instance
private void gridView1_CustomColumnDisplayText(object sender, DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventArgs e)
{
// gcLink is your column using repositoryitemhyperlinkedit
if (e.Column == gcLink)
{
var person = gridView1.GetRow(e.RowHandle) as Person;
if (person != null)
{
// Logic to show/hide the link based on other field
if (person.FirstName == "John")
e.DisplayText = string.Empty;
else
e.DisplayText = person.Link;
}
}
}
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 am grouping datagrid upto one sub-level.
Like this:
CollectionViewSource pageView = new CollectionViewSource();
pageView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
pageView.GroupDescriptions.Add(new PropertyGroupDescription("SubCategory"));
tasksDataGrid.ItemsSource = pageView.View;
In my case some records doesn't have Subcategory value.Those records will display under empty row group header of Subcategory in datagrid.
I would like to display directly under Category row group header instead of empty header.
private void TaskDataGrid_LoadingRowGroup(object sender, DataGridRowGroupHeaderEventArgs e)
{
string RowGroupHeader = // how to get currently loading header value
if(RowGroupHeader == string.Empty)
{
e.RowGroupHeader.Height = 0;
}
}
I can't get currently loading RowGroupHeader value.How can i get RowGroupHeader value in LoadingRowGroup event.
Help me on this.
This solved the problem.
private void TaskDataGrid_LoadingRowGroup(object sender, DataGridRowGroupHeaderEventArgs e)
{
var RowGroupHeader = (e.RowGroupHeader.DataContext as CollectionViewGroup);
if (RowGroupHeader != null && RowGroupHeader.Items.Count != 0)
{
MasterTask task = RowGroupHeader.Items[0] as MasterTask;
if (task != null && task.SubCategoryName == null)
e.RowGroupHeader.Height = 0;
}
}
Thanks djohnsonm for your help.
Try this, but insert the name of your VM and Property that would correspond to the Header value.
private void TaskDataGrid_LoadingRowGroup(object sender, DataGridRowGroupHeaderEventArgs e)
{
string RowGroupHeader = (e.RowGroupHeader.DataContext as ParentVM).VMProperty
if(RowGroupHeader == string.Empty)
{
e.RowGroupHeader.Height = 0;
}
}
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;
}