Implement a c# windows forms Autocomplete textbox with multiple suggested columns - c#

I am attempting to implement a Windows Forms control in C# that resembles a textbox. When the user types 3 or more characters, a search will be performed against a datasource. There will be multiple fields returned (see the class structure below as one possible definition).
public class MyStructure
{
public int Value1 { get; set; }
public string Value2 { get; set; }
public string Value3 {get; set; }
}
My requirements are to display an autocomplete list containing multiple columns (Note: this can be a string that contains padded fields from the list that are concatenated together). When the user either types all characters, hits the down arrow to select an item, or hits the enter key the value in the textbox will take the ValueMember of the list (where the DisplayMember of the list would be the whole data source). Every keystroke that the user enters that is not an up or down arrow or the enter key will perform another search and refresh the list.
I have seen how to implement a textbox with a single column in an auto-suggest, but cannot find a relatively "simple" example of how to do this for multiple columns. Should the control be a textbox or a combobox that is somehow styled to resemble a textbox (if this is possible) or a user control?
Should the event to monitor keystrokes be the TextEntered or the KeyPress event? Can I reset the AutoCompleteStringCollection without having the contents entered affected (I keep losing my input or my place in the input in any attempts)?
Can anyone provide examples of how to do this in framework 4.0 or above or point me to an example?
EDIT 1:
After much searching, I have found that essentially I need to implement a ContextMenuStrip on the TextBox (anything else and other controls below the user control will be overlapped). My problem is that I cannot determine how to handle the Key press events such as Tab and Enter. In addition, I need to handle if the user continues typing (in this event, I want to switch focus back to the textbox and add the key). Below is my code:
private void textBox1_TextChanged(object sender, EventArgs e)
{
ContextMenuStrip menuStrip;
string szMenuItem = string.Empty;
// This would actually be a call to a web service
List<MStarDeal> deals = DealInfo.Where(i => i.Value1.StartsWith(textBox1.Text.ToUpper()) || i.Value2.StartsWith(textBox1.Text.ToUpper()) || i.Value3.StartsWith(textBox1.Text.ToUpper()))
.Select(i => i).ToList();
if (textBox1.Text.Length >= 3 && !bSelected)
{
menuStrip = new System.Windows.Forms.ContextMenuStrip();
foreach (MStarDeal item in deals)
{
szMenuItem = item.Value1.PadRight(15) + item.Value2.PadRight(20) + item.Value3.PadRight(80);
ToolStripItem tsItem = new ToolStripMenuItem();
tsItem.Text = szMenuItem;
tsItem.Name = item.Value1;
tsItem.MouseUp += tsItem_MouseUp;
menuStrip.Items.Add(tsItem);
}
textBox1.ContextMenuStrip = menuStrip;
textBox1.ContextMenuStrip.Show(textBox1, new Point(0, 20));
}
else if (bSelected)
{
bSelected = false;
}
}
void tsItem_MouseUp(object sender, EventArgs e)
{
bSelected = true;
textBox1.Text = ((ToolStripMenuItem)sender).Name;
}
Thanks,
Lee

I think I understand your question. How about using the TextChanged() event instead of KeyPress? As far as the columns, a flowLayoutPanel will render columns if you set it up to flow in the right direction and make its size appropriate to the width of the two columns combined.

Related

C# - DropDown ComboBox - how can a user add value using nothing else?

I would like to solve my problem using only a DropDown ComboBox component. Although I have a list of colors, I want to allow user to enter an RGB color code.
In my imagination it would work two ways:
The user opens the Dropdown section and choses the wanted color
The user enters an RGB code (eg. 255;0;123) through the component's editable first line (then presses enter)
(I don't need (neither want) the RGB code to be added to the list of the ComboBox.)
I only need the results of that; I can process the outcoming data.
You need to use the ComboBox's "KeyDown" event. In the below I'm using "exampleProcess" as a method used when you have a colour you wish to use. I'm going to add an array of your example colours as comparisons also.
string[] colours = new string[]{"Red","Green","Blue","Yellow","etc"};//These would be the values in your combobox dropdown list.
Color selectedcolour;
private void ComboBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)//If enter key pressed.
{
if (colours.Contains(ComboBox.Text))//If the colour is one of the default colours.
{
selectedcolour = Color.FromName(ComboBox.Text);
}
else
{
List<string> parts = ComboBox.Text.Split(';');//Split text into parts between each ";".
foreach(string part in parts)
{
if (part == "")
{
parts.Remove(part);
}
}
int r = int.Parse(parts[0]);
int g = int.Parse(parts[1]);
int b = int.Parse(parts[2]);
selectedcolour = Color.FromArgb(r,g,b);
}
exampleProcess(selectedcolour);
}
}
You'll have to add more error checking but I think this should essentially work ^_^.

Using a ComboBox, UserControl and some forms to change language (for entire project)

Before I begin, I have researched and can't seem to find anything. Note I am very new to UserControl so this might be why it's proven difficult.
I have a combobox in Form1 which when selected allows the user to change between a choice of 21 languages. I have created a UserControl that contains labels, buttons and checkboxes - adds to a form called Print.
If a user selected French, how would I then implement the UserControl to change language for ALL forms in my project?
UserControl:
I have used a get and set method here for a button. When the language is changed in Form1, I want this button (all elements really) to change.
using System.Windows.Forms;
namespace Print
{
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string LabelPreview
{
get
{
return Button_Preview.Text;
}
set
{
Button_Preview.Text = value;
}
}
}
}
Form1:
If string value English is selected in the combobox, call a method - here is where I would like to change language for other forms.
private void ComboBoxLang_SelectedIndexChanged(object sender, EventArgs e)
{
string selectedItem = this.comboBoxLang.GetItemText(this.comboBoxLang.SelectedItem);
if (selectedItem == Language.English)
{
ToEnglish();
}
}
private void ToEnglish()
{
// Cannot actually implement the UserControl, It can't find the method above.
// When I've tried to implement UserControl in Print, it can't seem to find it either.
// I've tried:
// Print.UserControl1.(_LabelPreview doesn't show_);
// ^ It might be the completely wrong thing to do so excuse me.
}
I'm so confused... Do I program in Print (where the UserControl is added) or/and Form1?! I don't want the design to appear in Form1, but just want to let the other forms know what language has been selected.
Note: I have been using Unicode when translating*
How to trigger application-wide language change I described here on your other question Everytime ComboBox is changed (using SelectedIndexChanged) display message in other forms, if opened, of new value
Now, to set controls... One way of doing it is to create Database of phrases with StringId in one table and the StringId, LanguageId, StringValue in another. You would create StringManager object, which will have method GetLanguageSpecificString(stringId, languageId). When language change is triggered, your controls will call GetLanguageSpecificString fro each label you display, etc.
So your data will be like
Table DisplayLanguage
LanguageId Int
LanguageName nvarchar
LanguageCulture varchar
//1, English, us-En
//2, French, fr-Ca
Table DisplayString
StringId Int
//1
//2
//3
Table DisplayStringValue
DisplayStringValueId int
StringId int
LanguageId int
StringValue nvarchar
//1, 1, 1, Person Name
//2, 1, 2, Nome de Persona(or whateever)
Create cache using
"Select * from DisplayStringValue where LanguageId = 1"
And then use Linq or something to select for each control its data from cache because you don't want to hit DB with these for each control
"Select StringValue from DisplayStringValue where StringId = 1 and LanguageId = 1"
Now, combine my other answer with this and you will see that if in your form you have
LanguageChangeObserver.LanguageChanged += MyObserverHandler;
private void MyObserverHandler(languageId)
{
_formLanguage = languageId;
// set your controls
lblFirstName.Text = GetLanguageSpecificString(5, languageId);
lblLastName.Text = GetLanguageSpecificString(6, languageId);
// loop through userControls and pass to them language id
}
It would be good idea if your user controls would derive from the single base class that you create and which has already SetNewLanguage Method, so you could do
foreach (var c in form.controls)
{
MyControlBase currControl = c as MyControlBase;
if (currControl != null) currControl.SetNewLanguage(languageId);
}
So I've come up with a solution that works for me! I've copied across from the Printer.cs form where I have used a parameter to represent the language chosen, initiated strTextBox to equal label1 and included an if statement to see if the language is English (also working with UserControl to get the value of labels etc.).
Printer
public Printer(string strTextBox)
{
InitializeComponent();
label1.Text = strTextBox;
if (label1.Text == Language.English)
{
UserControl111.Label_Option_Multi = "Please select an option:"; //Simple test
}
}
Form1
private void Print_Click(object sender, EventArgs e)
{
string selectedItem = this.ComboBox_Lang.GetItemText(this.ComboBox_Lang.SelectedItem);
Printer p = new Printer(selectedItem);
p.Show();
}
UserControl
public string Label_Option_Multi
{
get
{
return Label_Option.Text;
}
set
{
Label_Option.Text = value;
}
}
As a result, if I select English in Form1.s then open up Printer.cs, the label displays English and translates accordingly.
You have to do it same way as for any other control. Imagine you have created your TextBox and now want all your textboxes in the project on each form to do something.
Obviously, you have to get a list of such controls somehow. One approach is to use Application.Forms to iterate through everything. Other is to register your control (add to a list) every time when it's created or shown or what_you_need and de-register (remove from a list) otherwise.

The BindingList Datasource of a Combobox refreshes correctly but the Combobox displays items in the wrong order

I have a BindingList< KeyValuePair < string, string > > that is bound to a ComboBox control. Based on some conditions, the BindingList will be added a new KeyValuePair. Now, the Newly added item shows up at index 0 of the Combobox, instead of at the end.
While debugging, I found that the BindingList has got the right order. (i.e, the new KeyValuePair is appended)
Also, I check the SelectedValue of the ComboBox in it's SelectedIndexChanged handler and it seems to be not of the ListItem that got selected. Instead, it is that of the supposed ListItem, if the ComboBox had got the right order as in its DataSource, - the BindingList..
The code is a small part of a large project.. Plz let me know if the question is not clear. I can put the relevant parts of the code as per our context.
How could something like this happen? What can I do differently?
I have this class something like this.
public class DropdownEntity
{
//removed all except one members and properties
private string frontEndName
public string FrontEndName
{
get {return this.frontEndName; }
set {this.frontEndName= value; }
}
//One Constructor
public DropdownEntity(string _frontEndName)
{
this.FrontEndName = _frontEndName;
//Removed code which initializes several members...
}
//All methods removed..
public override string ToString()
{
return frontEndName;
}
}
In my windows form, I have a tab control with several tabs. In one of the tabs pages, I have a DataGridView. The user is supposed to edit the cells and click on a Next - button. Then, some processing will be done, and the TabControl will be navigated to the next tab page.
The next tab page has the combobox that has the problem I mentioned. This page also has a back button, which will take back.. the user can modify the gridview cells again.. and click on the next button. This is when the order gets messed up.
I am posting here the Click event handler of the Next Button.. Along with the class, with the rest of the code removed.
public partial class AddUpdateWizard : Form
{
//Removed all members..
BindingList<KeyValuePair<string, string>> DropdownsCollection;
Dictionary<string, DropdownEntity> DropdownsDict;
//Defined in a partial definition of the class..
DataGridView SPInsertGridView = new DataGridView();
ComboBox DropdownsCmbBox = new ComboBox();
Button NextBtn2 = new Button();
Button BackBtn3 = new Button();
//Of course these controls are added to one of the panels
public AddUpdateWizard(MainForm mainForm)
{
InitializeComponent();
DropdownsDict = new Dictionary<string, DropdownEntity>();
}
private void NextBtn2_Click(object sender, EventArgs e)
{
string sqlArgName;
string frontEndName;
string fieldType;
for (int i = 0; i < SPInsertGridView.Rows.Count; i++)
{
sqlArgName = "";
frontEndName = "";
fieldType = "";
sqlArgName = SPInsertGridView.Rows[i].Cells["InsertArgName"].Value.ToString().Trim();
if (SPInsertGridView.Rows[i].Cells["InsertArgFrontEndName"].Value != null)
{
frontEndName = SPInsertGridView.Rows[i].Cells["InsertArgFrontEndName"].Value.ToString().Trim();
}
if (SPInsertGridView.Rows[i].Cells["InsertArgFieldType"].Value != null)
{
fieldType = SPInsertGridView.Rows[i].Cells["InsertArgFieldType"].Value.ToString().Trim();
}
//I could have used an enum here, but this is better.. for many reasons.
if (fieldType == "DROPDOWN")
{
if (!DropdownsDict.ContainsKey(sqlArgName))
DropdownsDict.Add(sqlArgName, new DropdownEntity(frontEndName));
else
DropdownsDict[sqlArgName].FrontEndName = frontEndName;
}
else
{
if (fieldType == "NONE")
nonFieldCount++;
if (DropdownsDict.ContainsKey(sqlArgName))
{
DropdownsDict.Remove(sqlArgName);
}
}
}
//DropdownsCollection is a BindingList<KeyValuePair<string, string>>.
//key in the BindingList KeyValuePair will be that of the dictionary.
//The value will be from the ToString() function of the object in the Dictionary.
DropdownsCollection = new BindingList<KeyValuePair<string,string>>(DropdownsDict.Select(kvp => new KeyValuePair<string, string>(kvp.Key, kvp.Value.ToString())).ToList());
DropdownsCmbBox.DataSource = DropdownsCollection;
DropdownsCmbBox.DisplayMember = "Value";
DropdownsCmbBox.ValueMember = "Key";
//Go to the next tab
hiddenVirtualTabs1.SelectedIndex++;
}
private void BackBtn3_Click(object sender, EventArgs e)
{
hiddenVirtualTabs1.SelectedIndex--;
}
//On Selected Index Changed of the mentioned Combobox..
private void DropdownsCmbBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (DropdownsCmbBox.SelectedValue != null)
{
if (DropdownsDict.ContainsKey((DropdownsCmbBox.SelectedValue.ToString())))
{
var dropdownEntity = DropdownsDict[DropdownsCmbBox.SelectedValue.ToString()];
DropdownEntityGB.Text = "Populate Dropdowns - " + dropdownEntity.ToString();
//Rest of the code here..
//I see that the Datasource of this ComboBox has got the items in the right order.
// The Combobox's SelectedValue is not that of the selected item. Very Strange behavior!!
}
}
}
}
The very first time the user clicks the Next Button, it's fine. But if he clicks the Back Button again and changes the Data Grid View cells.. The order will be gone.
I know, it can be frustrating to look at. It's a huge thing to ask for help. Any help would be greatly appreciated!
Please let me know if you need elaboration at any part.
Thanks a lot :)
I think you have two problems here.
First, if you want to retain the order of the items you should use an OrderedDictionary instead of a regular one. A normal collection will not retain the order of the items when you use Remove method. You can see more info about this related to List here.
You could use such dictionary like this:
DropDownDict = new OrderedDictionary();
// Add method will work as expected (as you have it now)
// Below you have to cast it before using Select
DropDownCollection = new BindingList<KeyValuePair<string, string>>(DropDownDict.Cast<DictionaryEntry>().Select(kvp => new KeyValuePair<string, string>(kvp.Key.ToString(), kvp.Value.ToString())).ToList());
The second problem could be that you change the display name (FrontEndName) of already existing items, but the key is preserved. When you add a new item, try to remove the old one that you're not using anymore and add a new item.
The Sorted Property of the Combobox is set to True! I didn't check that until now. I messed up. Terribly sorry for wasting your time Adrian. Thanks a lot for putting up with my mess here.. :)

How to make Microsoft.VisualBasic.PowerPacks.DataRepeater instantly update bound data?

Here I'm talking about Windows Forms Application written in C#. Consider a simple model
class Labelled
{
private string label;
public string Label
{
get { return label; }
set
{
if (label != value)
{
string message = String.Format(
"Label changed from {0} to {1}",
label, value
);
MessageBox.Show(message);
label = value;
}
}
}
public Labelled(string label)
{
this.label = label;
}
}
class Model
{
public Labelled SingularLabelled { get; set; }
public List<Labelled> ListedLabelled { get; set; }
public Model()
{
SingularLabelled = new Labelled("Singular");
ListedLabelled = new List<Labelled>();
for (int i = 1; i <= 10; ++i)
ListedLabelled.Add(new Labelled("Listed " + i.ToString()));
}
}
We have a simple class Labelled with string property Label and class Model with member SingularLabelled of type Labelled and ListedLabelled which is a list of Labelled's.
Now I want to display the data to the user. Here is my setup:
The main window has a TextBox for displaying SingularLabelled.Label and a DataRepeater from the Visual Basic PowerPacks to display labels of the elements of ListedLabelled. The ItemTemplate of the DataRepeater consists of just a single TextBox.
Let's focus on one way binding, namely I want the underlying data to be updated when the User changes the contents of a text box. The Label property of the Labelled raises a notification in form of a message box, so I can get to know exactly when the data is being updated. Now the arrows represent bindings. Blue arrows stand for data source and the red ones for data members. An instance of Model is created and bound to the modelBindingSource in the constructor of the main window form.
And here we come to a very important thing. I want the data to be updated immediately in sync with what the User is typing, so I made sure that the data source update modes of the data bindings are set to OnPropertyChanged. The generated code that might be of interest here is
this.singularTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.modelBindingSource, "SingularLabelled.Label", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
this.listedTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.listedLabelledBindingSource, "Label", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));
And this is working as expected when typing into the text box of SingularLabelled but the text boxes within DataRepeater trigger the update only when they loose focus. I want them to behave like the singular one. Ideal solution would be to do it using the designer. Does anyone know how to do this?
Above is a sample of the program working. Notice how SingularLabelled's label is updated every character put in and the members of ListedLabelled get the whole edited chunk updated after the corresponding text box looses focus.
We were able to overcome this limitation of DataRepeater by simulating the Tab key.
private void listedTextBox_TextChanged(object sender, EventArgs e)
{
//simulate tab key to force databinding
SendKeys.Send("{TAB}");
SendKeys.Send("+{TAB}");
}

Windows DataGridView _RowCommand

My background is pretty much ASP.Net, and I was asked to develop a small windows application. I tried to use a grid to present and select data, and I tough that the equivalent in windows forms to the ASP.Net's GridView was DataGridView. I'm not sure yet if that is the case, basically, in ASP.Net, you have the _RowCommand event associated to the grid, that is triggered after a Commandbutton is clicked. I noticed also that there is no such thing as a DataKeyNames property, so I don't know how to pass the current row key to the button clicked. Any help will be appreciated, thanks!
I forgot to mention: My grid has two DataGridViewButton type of columns, And I don't know the event that I need to code on to perform the selected command
The event you are looking for is the CellClick event - with the DataGridViewButtonColumn you do not actually associate event handlers to the particular buttons as you would with other buttons on your form.
From the event arguments returned by the cell click event you can then work out which row and column the click was in and from that what action you want to take.
private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
DataGridViewButton cell = (DataGridViewButtonCell)
dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];
// Any additional logic you want
}
Beyond that however, I think you would really benefit from taking a step back and thinking about the differences between the winforms and webforms coding paradigms.
With webforms a lot of the way you code is dictated by the fact that everything is stateless. This is not the case with winforms, you have state and you can access most controls when you need to find out information about them.
For this reason, retrieving information like the currently selected cell in a DataGridView is trivial with winforms.
Also, in most cases with winforms you do not need specific buttons to edit or delete - you can edit directly in the grid, and use in inbuilt delete functionality (select the row and press the delete key).
A good place the get you past some of the bumps might be the DataGridView FAQ. It's an amazing resource for learing about the DataGridView
DataGridView.CurrentRow gets the selected row. Is that what you need?
If you are databound, you should have (via .CurrentRow) access to all the properties, by casting .CurrentRow.DataBoundItem to whatever type you need. Otherwise, just look at the cells, or set a .Tag against the rows when you add them.
Here's an example showing a data-bound DataGridView and a couple of buttons, pulling out data for the selected row:
using System;
using System.ComponentModel;
using System.Windows.Forms;
class Person
{
public string Name { get; set; }
[DisplayName("Eye Color")]
public string EyeColor { get; set; }
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
using (var form = new Form())
using (var grid = new DataGridView { Dock = DockStyle.Fill})
using (var btn1 = new Button { Dock = DockStyle.Bottom, Text = "Button 1"})
using (var btn2 = new Button { Dock = DockStyle.Bottom, Text = "Button 2" })
{
btn1.Click += delegate
{
form.Text = "Button 1 clicked";
if (grid.CurrentRow != null)
{
form.Text += ": " + ((Person)grid.CurrentRow.DataBoundItem).Name;
}
};
btn2.Click += delegate
{
form.Text = "Button 2 clicked";
if (grid.CurrentRow != null)
{
form.Text += ": " + ((Person)grid.CurrentRow.DataBoundItem).Name;
}
};
form.Controls.Add(btn1);
form.Controls.Add(btn2);
form.Controls.Add(grid);
var data = new BindingList<Person>
{
new Person { Name = "Fred", EyeColor = "green"},
new Person { Name = "Barney", EyeColor = "brown"},
new Person { Name = "Wilma", EyeColor = "blue"},
new Person { Name = "Betty", EyeColor = "blue"},
};
grid.DataSource = data;
Application.Run(form);
}
}
}
There are other ways of handling the click events, and usually much of the above would be done via the designer rather than like this (but it is very hard to show designer code in a snippet).

Categories

Resources