How can I to manipulate dynamically created controls? - c#

Lets say I have a form where someone can catalog a multiple people's Name and their State and City?
Assume I already have all the State/Cities in database. (a table for each state, with cities listed in each table)
The Name field will be a TextBox. And the State & City fields will be ComboBox (DropDownLists).
One row (for the entry of one person) already exists in the form. But I want the user to be able to dynamically add rows of entries by pressing an "Add Person" button.
The next step is where I'm struggling. In each dynamically added row of fields, I would like the second ComboBox (Cities) to be populated depending on which State is chosen in the first Combo Box. Also, the Cities ComboBox will remain disabled until the State ComboBox is chosen.
My code looks something like this:
public ComboBox cbState;
public ComboBox cbCities;
public static int NumberOfPeople = 1;
private void btnAddNewPerson_Click(object sender, EventArgs e)
{
NumberOfPeople++;
TextBox txtPerson = new TextBox();
txtPerson.Name = "Person" + NumberOfPeople;
Panel.Controls.Add(txtPerson);
// ADD State ComboBox
cbState = new ComboBox();
cbState.Name = "State" + NumberOfPeople;
cbState.Enabled = true;
cbState.DropDownStyle = ComboBoxStyle.DropDownList;
Panel.Controls.Add(cbState);
// ADD City ComboBox
cbCity = new ComboBox();
cbCity.Name = "City" + NumberOfPeople;
cbCity.DropDownStyle = ComboBoxStyle.DropDownList;
cbCity.Enabled = false;
cbCity.SelectedValueChanged += new System.EventHandler(this.ChangeState);
Panel.Controls.Add(cbCity);
}
private void ChangeState(object sender, EventArgs e)
{
..... Don't know how to properly identify the City ComboBox that is in the same row as the State ComboBox that was just changed, and manipulate/populate it.....
}
Anyone able to help me solve this issue??
I'd greatly appreciate it!!

You could use a dictionary
Dictionary<ComboBox,ComboBox> CityToStateMap = new Dictionary<ComboBox,ComboBox>();
then when you add a row
CityToStateMap[cbState] = cbCity;
then when you changestate
ComboBox city = CityToStateMap[(ComboBox)sender];

First of all I would create an additional class (e.g. with name Row) that contains all controls of one row. So you can encapsulate controls of one person in one object.
Your Form class gets a list member of such row objects like
List<Row> _rows = new List<Row>();.
In constructor of that class Row you create the controls of one row and assign the event handlers to the SelectedValueChanged event of the combobox controls.

You could use the Control.Tag in order to attach a data object to your controls. See how the controls are introduced to each other in the example.
private void btnAddNewPerson_Click( object sender, EventArgs e )
{
AddPersonRow();
}
private void AddPersonRow()
{
// Create combo boxes
ComboBox cbCity = new ComboBox();
ComboBox cbState = new ComboBox();
// Introduce them to each other
cbCity.Tag = cbState;
cbState.Tag = cbCity;
// ADD State ComboBox
cbState.Name = "State" + NumberOfPeople;
cbState.Enabled = true;
cbState.DropDownStyle = ComboBoxStyle.DropDownList;
cbState.SelectedValueChanged += new EventHandler( cbState_SelectedValueChanged );
panel.Controls.Add( cbState );
// Populate the states sombo
PopulateStateCombo( cbState );
// ADD City ComboBox
cbCity.Name = "City" + NumberOfPeople;
cbCity.DropDownStyle = ComboBoxStyle.DropDownList;
cbCity.Enabled = false;
cbCity.SelectedValueChanged += new EventHandler(cbCity_SelectedValueChanged);
panel.Controls.Add( cbCity );
}
void cbState_SelectedValueChanged( object sender, EventArgs e )
{
ComboBox cbState = sender as ComboBox;
ComboBox cbCity = cbState.Tag as ComboBox;
cbCity.Enabled = true;
// .. Go ahead and populate cbCity by cbState's selected value ..
}
void cbCity_SelectedValueChanged( object sender, EventArgs e )
{
// Up to you...
}
Note that instead of shaking hands with each other ComboBox, you could create a class that will hold both ComboBoxes, your TextBox control, the row number, etc. and then set the Tag of all these controls to the class itself.
public class PersonRow
{
public int RowNum { get; private set; }
public TextBox NameTextBox { get; private set; }
public ComboBox CityCombo { get; private set; }
public ComboBox StateCombo { get; private set; }
public PersonRow( int rowNum )
{
RowNum = rowNum;
// Create the controls
NameTextBox = new TextBox();
CityCombo = new ComboBox();
StateCombo = new ComboBox();
// Bind them to this instance
NameTextBox.Tag = this;
CityCombo.Tag = this;
StateCombo.Tag = this;
//.. continue as in the prev. example..
}
}

First of all you should find the name of the comboBox you want to manipulate.
then you can search a control in the panel which has the name we just found, then you can do what ever you want.
here is the code for that
private void ChangeState(object sender,EventArgs e){
ComboBox stateComboBox= (ComboBox)sender;
//find the name of target City ComboBox
string cityComboName = "City"+stateComboBox.Name.Substring(5); // 5 is the Length of 'State'
ComboBox cityComboBox=null;
foreach(Control cntrl in panel1.Controls){
if (cntrl.Name == cityComboName) {
cityComboBox= (ComboBox)cntrl;
break;
}
}
if (cityComboBox!= null) {
cityComboBox.Enabled = true;
// now you have the both cityComboBox and stateComboBox Of the same Row
}
}

Related

c# textbox shows object name instead of value

I have a form written in c#, with various drop down lists, but I'm having problems with a listbox on the form. I need to populate a textbox with the values selected from the listbox when I double click on them. I've got the click event working, but the textbox will only populate with the object name, not the value from the listbox
i.e.
'System.Windows.Controls.SelectedItemCollection'
instead of the actual value.
Here is the entire code block I'm working on:
I should have just done this at the start - here is the complete code block I'm working on:
else if (theValue.FieldName.Equals("UIPathList", StringComparison.OrdinalIgnoreCase) == true)
{
int nRow = 14;
Button theUIPathOptionsButton = new Button();
TextBox theOldValueTextBox = AddLabelAndOldValue(theHelper, nRow, theValue);
theOldValueTextBox.Text = theValue.OldValue.Replace(",", "," + Environment.NewLine);
theUIPathOuterStackPanel = new StackPanel
{
Visibility = Visibility.Visible,
Orientation = Orientation.Vertical,
Background = new SolidColorBrush(Colors.White),
ClipToBounds = true,
};
theUIPathOptionsInnerStackPanel = new StackPanel
{
Visibility = Visibility.Visible,
Orientation = Orientation.Horizontal,
Background = new SolidColorBrush(Colors.White)
};
theUIPathOuterStackPanel.ClipToBounds = true;
TextBox theNewTextBox = new TextBox
{
TabIndex = nRow,
TextWrapping = TextWrapping.Wrap,
AcceptsReturn = true,
};
theNewTextBox.Clear();
theNewTextBox.MouseDoubleClick += MultiLineChildDatapointList_HandleMouseDoubleClick;
theNewTextBox.Focusable = true;
theNewTextBox.HorizontalAlignment = HorizontalAlignment.Stretch;
theNewTextBox.Width = 365;
theNewTextBox.PreviewKeyDown += theGetMetadataHelper.Preview_KeyDown_IsMultilineText;
theNewTextBox.Tag = theValue;
ListBox theUIPathOptionslistBox = new ListBox();
theUIPathOptionslistBox.Items.Add("RuntimeDefaults");
theUIPathOptionslistBox.Items.Add("CommonSettings");
theUIPathOptionslistBox.Items.Add(InputDatapointManager.CONST_CHANGE_RECORD_CHANGES_CLEAR_VALUE);
theUIPathOptionslistBox.TabIndex = nRow;
theUIPathOptionslistBox.SelectionMode = SelectionMode.Multiple;
theUIPathOptionslistBox.ClipToBounds = true;
theUIPathOptionslistBox.Focusable = true;
theUIPathOptionslistBox.Visibility = Visibility.Hidden;
theUIPathOptionslistBox.Height = 34;
theUIPathOptionsInnerStackPanel.Children.Add(theNewTextBox);
theUIPathOptionsInnerStackPanel.Children.Add(theUIPathOptionsButton);
theUIPathOuterStackPanel.Children.Add(theUIPathOptionsInnerStackPanel);
theUIPathOuterStackPanel.Children.Add(theUIPathOptionslistBox);
void button1_click(object sender, EventArgs e)
{
theUIPathOptionslistBox.Visibility = Visibility.Visible;
}
void button1_doubleclick(object sender, EventArgs e)
{
theNewTextBox.Text = theUIPathOptionslistBox.SelectedItem.ToString();
}
theUIPathOptionsButton.Click += button1_click;
theUIPathOptionslistBox.MouseDoubleClick += button1_doubleclick;
Grid.SetColumn(theUIPathOuterStackPanel, 4);
Grid.SetRow(theUIPathOuterStackPanel, nRow);
theDataGrid.Children.Add(theUIPathOuterStackPanel);
theEditControlList.Add(theNewTextBox);
}
This was (possibly) already answered here : Getting value of selected item in list box as string
string myItem = listBox1.GetItemText(listBox1.SelectedItem);
Then you just have to add your item to the textbox :
textBox1.Text = myItem;
If you don't want to create a new string variable, then this one is working too :
textBox1.Text = listBox1.SelectedItem.ToString();
ListBox, Item is a collection of objects, not strings, so you must let it know how to convert it to string, otherwise it will use its defualt .ToString() function that obviously the object currently in your items not giving the desired result.
Imagine items are oftype following class:
class SomeClass
{
public int Id;
public string Name;
}
You may do one of these three:
1.set the DisplayMember of your ListBox to Name
2.add override method to your class so that it overrides its .ToString() and return its Name property:
class SomeClass
{
public int Id;
public string Name;
public override string ToString()
{
return Name;
}
}
3.Just cast it to its real type and get the property you want:
SomeClass selected = (SomeClass)ListBox.SelectedItem;
TextBox1.Text = selected.Name;
This is because the TextBox will use the ToString() method on what it is bound to, which by default will return the class name.
You solutions are to either override the ToString() method of the class to return the value you want or set the text property of the TextBox to the text value you want rather then the object.
The solution I finally got to was as follows:
void theUIPathOptionslistBox_SelectedIndexChanged(object sender,
SelectionChangedEventArgs e)
{
theNewTextBox.Clear();
foreach (object selectedItem in theUIPathOptionslistBox.SelectedItems)
{
theNewTextBox.AppendText(selectedItem.ToString() + Environment.NewLine);
}
}
theUIPathOptionslistBox.SelectionChanged +=
theUIPathOptionslistBox_SelectedIndexChanged;

WPF: Raise programmatically a SelectionChangedEvent with an object

MouseDoubleClick opens a new window in which I update the DataTable by changing the content of TextBoxes. After updating a DataTable, I need to raise a SelectionChangedEvent in order to update strings to the correct values (SelectionChangedEvent triggers when you select a row in the DataGrid). That would be simple enough, if I wasn't programmatically selecting the same row after refreshing the DataGrid, which means that the selection technically never changes and the values won't update unless I select another row.
I solved the issue by changing the index to -1 then changing it back to the previous value, but I would rather just call the handler directly DG_Part_SelectionChanged();. Refactoring the logic into a new function doesn't work.
public void DG_Part_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (CurrentPartID != 0)
{
int lastId = CurrentPartID;
EditWindow ew = new EditWindow(CurrentPartID)
{
Owner = this
};
ew.ShowDialog();
if (Global.invokeDataGridParts == "yes")
{
// Refreshes the datagrid with an updated datatable
InvokeDataGridPart();
// Finds and selects the new index position of the modified row
SqlPartsSetToRow(lastId);
// Scrolls into view
dg_part.ScrollToCenterOfView(dg_part.Items[dg_part.SelectedIndex]);
// Highlights the row
Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() =>
{
DataGridRow row = (DataGridRow)dg_part.ItemContainerGenerator.ContainerFromIndex(dg_part.SelectedIndex);
row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
));
// Restores index so that you may re-select the previous selection correctly
int saveIndex = dg_part.SelectedIndex;
dg_part.SelectedIndex = -1;
dg_part.SelectedIndex = saveIndex;
}
}
}
public void DG_Part_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataGrid gd = (DataGrid)sender;
if (gd.SelectedItem is DataRowView row_selected)
{
Global.del = row_selected["DEL"].ToString();
Global.delez = row_selected["DELEZ"].ToString();
Global.cr_tu = row_selected["CRTU"].ToString();
Global.st_clanov = row_selected["ST"].ToString();
Global.lastnik = row_selected["LASTNIK"].ToString();
Global.naslov = row_selected["NASLOV"].ToString();
Global.ps = row_selected["PS"].ToString();
Global.obmocje2 = row_selected["OBMOCJE"].ToString();
Global.drzava = row_selected["DRZAVA"].ToString();
Global.emso = row_selected["EMSO"].ToString();
Global.maticna_st = row_selected["MATICNA"].ToString();
Global.reference = row_selected["REFERENCE"].ToString();
Global.opis = row_selected["OPIS"].ToString();
Global.opomba = row_selected["OPOMBA"].ToString();
}
}
Had to change DataGrid gd = (DataGrid)sender; into DataGrid gd = (DataGrid)dg_part; and refactor the logic:
public void DG_Part_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DG_Part_Selection();
}
public void DG_Part_Selection()
{
DataGrid gd = (DataGrid)dg_part;
if (gd.SelectedItem is DataRowView row_selected)
{
Global.del = row_selected["DEL"].ToString();
Global.delez = row_selected["DELEZ"].ToString();
Global.cr_tu = row_selected["CRTU"].ToString();
Global.st_clanov = row_selected["ST"].ToString();
Global.lastnik = row_selected["LASTNIK"].ToString();
Global.naslov = row_selected["NASLOV"].ToString();
Global.ps = row_selected["PS"].ToString();
Global.obmocje2 = row_selected["OBMOCJE"].ToString();
Global.drzava = row_selected["DRZAVA"].ToString();
Global.emso = row_selected["EMSO"].ToString();
Global.maticna_st = row_selected["MATICNA"].ToString();
Global.reference = row_selected["REFERENCE"].ToString();
Global.opis = row_selected["OPIS"].ToString();
Global.opomba = row_selected["OPOMBA"].ToString();
}
}
Then simply call the handler:
public void DG_Part_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (CurrentPartID != 0)
{
int lastId = CurrentPartID;
EditWindow ew = new EditWindow(CurrentPartID)
{
Owner = this
};
ew.ShowDialog();
if (Global.invokeDataGridParts == "yes")
{
// Refreshes the datagrid with an updated datatable
InvokeDataGridPart();
// Finds and selects the new index position of the modified row
SqlPartsSetToRow(lastId);
// Scrolls into view
dg_part.ScrollToCenterOfView(dg_part.Items[dg_part.SelectedIndex]);
// Highlights the row
Dispatcher.Invoke(DispatcherPriority.SystemIdle, new Action(() =>
{
DataGridRow row = (DataGridRow)dg_part.ItemContainerGenerator.ContainerFromIndex(dg_part.SelectedIndex);
row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
));
// Restores index so that you may re-select the previous selection correctly
DG_Part_Selection();
}
}
}

WPF How to create custom DataGrid with custom DataGridCells?

I'm looking to create a custom DataGrid such that users can attach notes to each cell using a popup input box. Currently I've created a CustomDataGrid class, inheriting from DataGrid, with a ContextMenu that has an option to add a note. When the user chooses to add a note, I find the selected cell, an input box is opened and returns the response, and I store it in a list of lists of strings, where each list of string represents a row. This doesn't work all the time, however, because sometimes no cell is selected, and I get an error message saying: 'Object reference not set to an instance of an object.'. I'm thinking of creating a CustomDataGridCell class, inheriting from DataGridCell, which has its own ContextMenu and note string. The question is, how would I make all cells in my CustomDataGrid a CustomDataGridCell? Is there a better way to do this?
Here is my current CustomDataGrid class:
public class CustomDataGrid : DataGrid
{
MenuItem miAddNote;
List<List<string>> notes;
public CustomDataGrid()
{
notes = new List<List<string>>();
miAddNote = new MenuItem();
miAddNote.Click += MiAddNote_Click;
miAddNote.Header = "Add a note";
this.ContextMenu = new ContextMenu();
this.ContextMenu.Items.Add(miAddNote);
}
private void MiAddNote_Click(object sender, RoutedEventArgs e)
{
try
{
int rowIndex = this.SelectedIndex;
int colIndex = this.SelectedCells[0].Column.DisplayIndex;
InputBox ib = new InputBox(notes[rowIndex][colIndex]);
if (ib.ShowDialog() == true)
{
notes[rowIndex][colIndex] = ib.Response;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
protected override void OnLoadingRow(DataGridRowEventArgs e)
{
base.OnLoadingRow(e);
int numColumns = this.Columns.Count;
List<string> newRow = new List<string>();
for (int i = 0; i < numColumns; ++i)
{
newRow.Add("");
}
notes.Add(newRow);
}
}
The question is, how would I make all cells in my CustomDataGrid a CustomDataGridCell?
There is no easy way to to this I am afraid. And it is not really necessary to create a custom cell type just to get rid of the exception.
Is there a better way to do this?
You should simply check whether there are any selected cells before trying to access any:
private void MiAddNote_Click(object sender, RoutedEventArgs e)
{
int rowIndex = this.SelectedIndex;
if (rowIndex != -1 && SelectedCells != null && SelectedCells.Count > 0)
{
int colIndex = this.SelectedCells[0].Column.DisplayIndex;
InputBox ib = new InputBox(notes[rowIndex][colIndex]);
if (ib.ShowDialog() == true)
{
notes[rowIndex][colIndex] = ib.Response;
}
}
}

Use Listbox and checkbox like CheckBoxList from .NET in Windows Phone

Im sorry for asking this basic question, but im tired of searching everything and i cant find anything usefull.
I want to use one list box with one checkbox in the datatemplate like the CheckBoxList in .Net, the only thing i want to do is: have the list of checkboxes with the name on front of it, select one, and on one button click event check the items inside the listbox and retrieve the text of the selected checkboxes.
Im a .NET programmer, but this is starting to anoying me.
Here is the ListBox XAML code.
<ListBox Height="299" Name="lstbEquipas" Width="432">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Name="ckbEquipa" Content="{Binding Designacao}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
this is what i use to populate the listbox and it works exactly like the CheckListBox in aspnet, a list of checkboxes with text on the front.
GameManager.GameManagerSoapClient GM;
public Page1()
{
InitializeComponent();
GM = new GameManager.GameManagerSoapClient();
GM.LerEquipasAsync();
GM.LerEquipasCompleted += new EventHandler<GameManager.LerEquipasCompletedEventArgs>(GM_LerEquipasCompleted);
}
void GM_LerEquipasCompleted(object sender, GameManager.LerEquipasCompletedEventArgs e)
{
XDocument data = XDocument.Load(new StringReader(e.Result));
var equipas = from query in data.Descendants("Equipas")
select new Equipa
{
ID = (int)query.Element("ID"),
Designacao = (string)query.Element("Designacao"),
};
lstbEquipas.ItemsSource = equipas;
}
And this is the classe used above.
public class Equipa
{
int iD;
string designacao;
public int ID
{
get { return iD; }
set { iD = value; }
}
public string Designacao
{
get { return designacao; }
set { designacao = value; }
}
}
All i want to do is in the button click event, get the text of the first selected checkbox in the ListBox. So i can send it to the webservice and save it to the database.
private void btnRegistar_Click(object sender, RoutedEventArgs e)
{
int i = 0;
foreach (ListBoxItem Item in this.lstbEquipas.Items)
{
//i want to check the checkbox were
}
//GM.InserirNovoUtilizadorAsync(this.txtLoginUtilizador.Text, this.txtLoginPassword.Password, 1);
//GM.InserirNovoUtilizadorCompleted += new EventHandler<GameManager.InserirNovoUtilizadorCompletedEventArgs>(GM_InserirNovoUtilizadorCompleted);
}
Any help would be appreciated
I'd suggest to store the data for ItemsSource in a property :
public ObservableCollection<Equipa> Equipas { get; set; }
void GM_LerEquipasCompleted(object sender, GameManager.LerEquipasCompletedEventArgs e)
{
XDocument data = XDocument.Load(new StringReader(e.Result));
var equipas = from query in data.Descendants("Equipas")
select new Equipa
{
ID = (int)query.Element("ID"),
Designacao = (string)query.Element("Designacao"),
};
Equipas = new ObservableCollection<Equipa>(equipas);
lstbEquipas.ItemsSource = Equipas;
}
Then you can get list of all checked items from above property using LINQ :
private void btnRegistar_Click(object sender, RoutedEventArgs e)
{
//get only checked items, and store it in variable
var checkedItems = Equipas.Where(o => o.IsSelected);
foreach (Equipa Item in checkedItems)
{
//here you can save current item to database
}
}

Leaving the dropdown enabled

I want to disable a combobox, but at the same time I want to let the users see the other options available (that is, I want to enable the dropdown).
By default, when ComboBox.Enabled = false, the dropdown is also disabled (nothing happens when we click on the combobox).
My first thought is to leave it enabled and handle the ComboBox.SelectedIndex event to set it back to the default value (I will just need to gray it out in some way.)
I am wondering if there is any native functionality like this that I am missing, or if there would be other way of doing it.
Don't use a Combobox if you don't want the Combobox functionality. Use a ListView instead.
A "What You See Is What You Can't Get" Combobox seems a bad idea.
I suggest using ListBox instead.
It's a hacky workaround, but it should accomplish something similar to your request:
public partial class Form1 : Form
{
ComboBox _dummy;
public Form1()
{
InitializeComponent();
// set the style
comboBox1.DropDownStyle =
System.Windows.Forms.ComboBoxStyle.DropDownList;
// disable the combobox
comboBox1.Enabled = false;
// add the dummy combobox
_dummy = new ComboBox();
_dummy.Visible = false;
_dummy.Enabled = true;
_dummy.DropDownStyle = ComboBoxStyle.DropDownList;
this.Controls.Add(_dummy);
// add the event handler
MouseMove += Form1_MouseMove;
}
void Form1_MouseMove(object sender, MouseEventArgs e)
{
var child = this.GetChildAtPoint(e.Location);
if (child == comboBox1)
{
if (!comboBox1.Enabled)
{
// copy the items
_dummy.Items.Clear();
object[] items = new object[comboBox1.Items.Count];
comboBox1.Items.CopyTo(items, 0);
_dummy.Items.AddRange(items);
// set the size and position
_dummy.Left = comboBox1.Left;
_dummy.Top = comboBox1.Top;
_dummy.Height = comboBox1.Height;
_dummy.Width = comboBox1.Width;
// switch visibility
comboBox1.Visible = !(_dummy.Visible = true);
}
}
else if (child != _dummy)
{
// switch visibility
comboBox1.Visible = !(_dummy.Visible = false);
}
}
}
If using a ListBox as other answers suggested is not convenient. There is a way by creating a custom combobox and adding a ReadOnly property. Try this code :
class MyCombo : System.Windows.Forms.ComboBox
{
public bool ReadOnly { get; set; }
public int currentIndex;
public MyCombo()
{
currentIndex = SelectedIndex ;
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (ReadOnly && Focused)
SelectedIndex = currentIndex;
currentIndex = SelectedIndex;
base.OnSelectedIndexChanged(e);
}
}
Usually the background color of read-only controls should not change, so I haven't done that part.

Categories