Windows DataGridView _RowCommand - c#

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).

Related

Directly give a new Control an EventHandler

I'm looping a collection of strings, coming from a database; for each entry, I create a new Button which is then added to a FlowLayoutPanel.
The Text of each Button is set to the current item in the string collection.
I'd like to assign an EventHandler to the Click event of each Button, however I am only able to access the Properties of a Button.
I have to cast the last entry of the FlowLayoutPanel's Controls collection to Button and add the Event Handler to this instance.
Does anyone know the reason why I can't access anything else then Properties? Is there a cleaner way of coding that?
List<string> temp = Database.GetNames();
foreach(string s in temp)
{
flp_main.Controls.Add(new Button()
{
Text = s.name
});
Button b = (Button)flp_main.Controls[flp_main.Controls.Count - 1];
b.Click += B_Click;
}
Asking for Improvements on Code Quality

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

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.

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}");
}

WinForms DataGridView - Full text display and dataSource update

Earlier today i was suggested in here to use a DataGridView to print messages that needed a individual mark as read.
I followed the suggestion, and with some reading online i managed to bind it to my message list with the following results after some tweaking.
alt text http://img237.imageshack.us/img237/3015/datagridview.jpg
Currently i have 2 issues, the first one is that i didn't find a way to resize the row height to display the full message, and the second one is that when the list is updated, the DataGridView doesn't display the modifications.
Any way to solve both problems? Or do i need to use something other than DataGridView, and in that case what should i be using?
Also, is there any way to urls contained in the message to become clickable and be opened in the default browser?
EDIT
More info in relation to the binding.
Basically i have a class variable inside the form, and i do the initial binding with a button.
private void button1_Click(object sender, EventArgs e)
{
list.Add(new Class1() { Message = "http://www.google.com/", Read = false });
list.Add(new Class1() { Message = "Message way too long to fit in this small column width", Read = false });
dataGridView1.DataSource = list;
}
I then have another button that adds some more entries just to test it, and i know the list is properly updated, but there are no changes in the dataGridView.
EDIT 2
If i wasn't clear before i need for the width to be fixed, and the cell height that contains the long text to be enlarged and display the text in 2 lines
have you checked the options in the EditColumn using smart tag ?
you can add column of type
DataGridViewLinkColumn, set its Text property to Message
Try removing any value from width
and height properties for a
column. In this way, it will set the
column size (cell) size according to
the data size.
hope this helps
I'll take a stab and see if I can help.
First off the row height. There are two DataGridView Methods called AutoResizeRow and AutoResizeRows which will adjust the height of the row to fit the contents.
Can you show us how you are binding your data to the DataViewGrid and how the data might be modified? That will help with the modifications not updating.
As for the link, unfortunately I can't seem to find an object which handles this sort of thing natively. Most likely you will first have to decide if the text going into the DataGridView is a link (using a Regular Expression, if you were me). Second, display it differently in the DataGridView (underline it, make it blue). Third, put a click event on it and when that cell is clicked handle that by throwing it out to a browser. I will look a little further into it though since this seems like a lot of work (and I will keep my fingers crossed that someone knows better than I do).
Regarding the list not updating; there are two issues;
To notice add/remove, you need list binding events. The easiest way to do this is to ensure you use a BindingList<YourClass> rather than a List<YourClass>.
To notice changes to individual properties (in this context) you'll need to implement INotifyPropertyChanged on your type:
public class YourClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private string message;
public string Message
{
get { return message; }
set { message = value; OnPropertyChanged("Message"); }
}
public bool isRead;
[DisplayName("Read")]
public bool IsRead
{
get { return isRead; }
set { isRead = value; OnPropertyChanged("IsRead"); }
}
}
For an example showing binding that to a list:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
BindingList<YourClass> list = new BindingList<YourClass>();
DataGridView grid = new DataGridView();
grid.Dock = DockStyle.Fill;
grid.DataSource = list;
Button add = new Button();
add.Text = "Add";
add.Dock = DockStyle.Bottom;
add.Click += delegate
{
YourClass newObj = new YourClass();
newObj.Message = DateTime.Now.ToShortTimeString();
list.Add(newObj);
};
Button edit = new Button();
edit.Text = "Edit";
edit.Dock = DockStyle.Bottom;
edit.Click += delegate
{
if (list.Count > 0)
{
list[0].Message = "Boo!";
list[0].IsRead = !list[0].IsRead;
}
};
Form form = new Form();
form.Controls.Add(grid);
form.Controls.Add(add);
form.Controls.Add(edit);
Application.Run(form);
}

Categories

Resources