I have two comboboxes where the first one has categories (and I can fill that easily from the source file). The trick is having the second combobox show only the items that are associated with the chosen category from the first combobox. For example:
cb1 is populated from a sourcefile with the category values 1, 2, 3 & 4 and cb2 is populated with values A,B,C,D,E,F,G,H
What I am failing at doing is limiting what is seen in cb2. So when cb1's value is "1", I only want "A" and "B" to be visible in cb2, and if cb1 changes to "2" I only want "C" and "D" to be visible.
For winforms :
If you have a form with 2 combo boxes (cb1, cb2) you could use something like this? (obviously modified to support your data objects).
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
//create a list for data items
List<MyComboBoxItem> cb1Items = new List<MyComboBoxItem>();
//assign sub items
cb1Items.Add(new MyComboBoxItem("1")
{
SubItems = { new MyComboBoxItem("A"), new MyComboBoxItem("B") }
});
cb1Items.Add(new MyComboBoxItem("2")
{
SubItems = { new MyComboBoxItem("C"), new MyComboBoxItem("D") }
});
cb1Items.Add(new MyComboBoxItem("3")
{
SubItems = { new MyComboBoxItem("E"), new MyComboBoxItem("F") }
});
cb1Items.Add(new MyComboBoxItem("4")
{
SubItems = { new MyComboBoxItem("G"), new MyComboBoxItem("H") }
});
//load data items into combobox 1
cb1.Items.AddRange(cb1Items.ToArray());
}
private void cb1_SelectedIndexChanged(object sender, EventArgs e)
{
//get the combobox item
MyComboBoxItem item = (sender as ComboBox).SelectedItem as MyComboBoxItem;
//make sure no shinanigans are going on
if (item == null)
return;
//clear out combobox 2
cb2.Items.Clear();
//add sub items
cb2.Items.AddRange(item.SubItems.ToArray());
}
}
public class MyComboBoxItem
{
public string Name;
public List<MyComboBoxItem> SubItems = new List<MyComboBoxItem>();
public MyComboBoxItem(string name)
{
this.Name = name;
}
public override string ToString()
{
return Name;
}
}
Related
how can two different items in a list be bound to one another hence when one is deleted its bound patner is also deleted.The question may sound simple, but the items are in two different lists.
public class MyClass{
public StackLayout SavedHoursLayout = new StackLayout {};
public Label RemoveHoursLabel;
public TapGestureRecognizer RemoveTapped;
public Grid HoursRemoveGrid;
public Button AddHoursButton = new Button();
public Label Correct = new Label{Text="Correct"};
public list<Label> ItemsLayout = new list<Label>();
public MyClass()
{
Content = new StackLayout
{
Children = { AddHoursButton,SavedHoursLayout }
}
AddHoursButton.Clicked+=AddHoursButton_Clicked;
}
public void AddSavedHours()
{
Label Time = new Label { };
RemoveHoursLabel = new Label {
Text="remove",TextColor=Color.Red,FontAttributes=FontAttributes.Italic};
HoursRemoveGrid = new Grid();
RemoveTapped = new TapGestureRecognizer();
this.BindingContext = HoursRemoveGrid;
HoursRemoveGrid.Children.Add(Time,0,0);
HoursRemoveGrid.Children.Add(RemoveHoursLabel,1,0);
SavedHoursLayout.Children.Add(HoursRemoveGrid);
RemoveHoursLabel.GestureRecognizers.Add(RemoveTapped);
RemoveTapped.Tapped += RemoveTapped_Tapped;
ItemsLayout.Children.Add(Correct);
void RemoveTapped_Tapped(object sender, EventArgs e)
{
var grid = (sender as Label).Parent as Grid;
int position = SavedHoursLayout.Children.IndexOf(grid);
SavedHoursLayout.Children.RemoveAt(position);
}
}
private void AddHoursButton_Clicked(object sender, System.EventArgs e)
{
AddSavedHours();
}
}
Question
Now when i click RemoveHourLabel, i want to remove the Label Correctin ItemsLayoutcorresponding to the RemovehoursGrid.
NB
There are already a number of Labels in ItemsLayout, so each Label Correct does not have the same index as that of its correspondent RemoveHoursGrid.
Solution:
You can create a new list<Label> to hold the labels so that it will have the same index as that of its correspondent RemoveHoursGrid.
First, create a new list, let's call it tempItemsLayout :
public List<Label> tempItemsLayout = new List<Label>();
Then in your method AddSavedHours, Add the correct to tempItemsLayout too:
public void AddSavedHours()
{
//...other codes
RemoveHoursLabel.GestureRecognizers.Add(RemoveTapped);
RemoveTapped.Tapped += RemoveTapped_Tapped;
ItemsLayout.Add(Correct);
//Add the correct to tempItemsLayout too
tempItemsLayout.Add(Correct);
//...other codes
}
Then in your RemoveTapped_Tapped, Remove the label from both tempItemsLayout and ItemsLayout:
void RemoveTapped_Tapped(object sender, EventArgs e)
{
var grid = (sender as Label).Parent as Grid;
int position = SavedHoursLayout.Children.IndexOf(grid);
SavedHoursLayout.Children.RemoveAt(position);
Label tempLabel = tempItemsLayout[position];
//Remove the label from both tempItemsLayout and ItemsLayout
tempItemsLayout.Remove(tempLabel);
ItemsLayout.Remove(tempLabel);
}
Try and let me know if it works.
I have a list of items that have a name, price and quantity value.
This list is stored in one form, and this form also had an edit button, so that when a user clicks on a row, they are able to edit this item inside another form that pops up.
I have my code working so that the item changes in the list, however it seems like the DataGridView just isn't updating when the list is changed.
When I edit an item, and add in a new row, it shows the changed values.
Here is my code for my first form:
private void EditButton_Click(object sender, EventArgs e)
{
EditForm editForm = new EditForm();
if (BasketGrid.RowCount > 0)
{
editForm.Show();
}
}
So this juts sets up the button so that it shows the other form.
"BasketGrid" is my DataGridView, that is also given a public initialization at the beginning of my code (Called dgv)
public void EditOkBut_Click(object sender, EventArgs e)
{
this.newName = editNameBox.Text;
decimal price;
int quant;
if (decimal.TryParse(editPriceBox.Text, out price))
{
this.newPrice = price;
}
else
{
MessageBox.Show("Incorrect format for price");
}
if(int.TryParse(editQuantBox.Text, out quant))
{
this.newQuantity = quant;
}
else
{
MessageBox.Show("Incorrect format for quantity");
}
foreach (OrderItem o in basketForm.GetList().ToList())
{
string listName = basketForm.getListName();
if (listName == o.ProductName)
{
o.ProductName = this.newName;
o.ProductPrice = this.newPrice;
o.ProductQuantity = this.newQuantity;
}
}
this.Close();
}
This is my "Edit Button" in my secondary form. This grabs my itemlist from my other form via a method, and compares the product name in of the orderitem in the list, and the listname that the user has selected from the row.
I'd created 'basketForm' as a new object of my other form, so I can access methods and stuff.
I've tried to use basketForm.dgv.Refresh(); but to no avail.
Any help is appreciated.
Cheers,
Daniel
You can use BindingSource and ShowDialog...
Example:
public partial class MainForm : Form
{
private BindingSource bindingSource = new BindingSource();
List<YourData> yourData = new List<YourData>();
public MainForm()
{
InitializeComponent();
bindingSource.DataSource = yourData;
dgv.DataSource = bindingSource;
}
}
Changes will be reflected to your grid like this...
private void EditButton_Click(object sender, EventArgs e)
{
EditForm editForm = new EditForm(yourData);
if (BasketGrid.RowCount > 0)
{
editForm.ShowDialog(this);
bindingSource.ResetBindings(true);
}
}
//Change your Data in EditForm whatever you want
public partial class EditForm : Form
{
List<YourData> yourData;
public EditForm(List<YourData> yourData)
{
InitializeComponent();
this.yourData = yourData;
}
}
You should implement INotifyPropertyChanged interface in the OrderItem class. This will update only one value in DataGridView, instead of updating the entire collection, which may be critical if the collection is very large and its binding may trigger actions, like validation, etc.
class OrderItem : INotifyPropertyChanged
{
private string name;
// other fields : price, quantity
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged();
}
}
}
// other properties: Price, Quantity
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Also you have to use BindingList class instead of List. It supports two-way data-binding mechanism.
I've set the combobox data in the c# code, I would like to write to the database with a number rather than the text in the combobox; for instance I would like to have:
1 - View Only = 1
2 - Basic User = 2
3 - Supervisor = 3
4 - Administrator = 4
5 - Super User = 5
I've no idea how to do this. Below is what I've got so far.
private void ComboBox_Loaded(object sender, RoutedEventArgs e)
{
// ... A List.
List<string> data = new List<string>();
data.Add("1 - View Only");
data.Add("2 - Basic User");
data.Add("3 - Supervisor");
data.Add("4 - Administrator");
data.Add("5 - Super User");
// ... Get the ComboBox reference.
var comboBox = sender as ComboBox;
// ... Assign the ItemsSource to the List.
comboBox.ItemsSource = data;
// ... Make the first item selected.
comboBox.SelectedIndex = 0;
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// ... Get the ComboBox.
var comboBox = sender as ComboBox;
// ... Set SelectedItem as Window Title.
string value = comboBox.SelectedItem as string;
this.Title = "Selected: " + value;
}
So to sum up my ideal outcome - When someone selects "1 - View Only" from the combobox it sets the user level in the database to 1.
Thanks
For something simplistic, you can use comboBox.SelectedIndex + 1, but that will break down if the order of your items changes somehow.
For something slightly more elaborate, you could do the following:
namespace ComboBoxExampleWPF
{
class UserLevel
{
public UserLevel(int level, string description)
{
Level = level;
Description = description;
}
public int Level { get; private set; }
public string Description { get; private set; }
// This controls how it shows up in the comboBox
public override string ToString()
{
return Level.ToString() + " - " + Description;
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<UserLevel> list = new List<UserLevel>()
{
new UserLevel(1, "View Only"),
new UserLevel(2, "Basic User"),
new UserLevel(3, "Supervisor"),
new UserLevel(4, "Administrator"),
new UserLevel(5, "Super User")
};
comboBox1.ItemsSource = list;
comboBox1.SelectionChanged += comboBox1_SelectionChanged;
}
void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// ... Get the ComboBox.
var comboBox = sender as ComboBox;
// ... Set SelectedItem as Window Title.
UserLevel value = comboBox.SelectedItem as UserLevel;
this.Title = "Selected: " + value.ToString();
SetUserLevel(value);
}
private void SetUserLevel(UserLevel ul)
{
// _myDatabase.SetUserLevel(ul.Level);
}
}
}
I'm attempting my first Windows Form project, having been entirely web based previously and experiencing some issues. I want to bind a list of objects to a TabControl and have this create the Tabs and then have a databound value accessible from the click event of each tab.
The Object I'm wanting to bind is
public class TreeNodeItem
{
private NTree<string> node;
public TreeNodeItem(NTree<string> node)
{
this.node = node;
}
public string Value
{
get { return this.node.data; }
}
}
The NTree node represents a node in an object that models data in a tree structure. I want to create a tab for each object in the list with the Value property being bound to the Tab Text property. Other posts mention binding to the ItemsSource property of the control, but Visual Studio is not giving me this property.
Any help will be greatly appreciated.
Cheers
Stewart
Okay, I was unaware of that the binding was a must. Although I have never seen something like this being done in a Windows Forms Application, I've decided to create a class that does this for us.
It uses the ObservableCollection<T> to keep track whether an object / property has been changed inside its list.
public class ObservableList<T> : ObservableCollection<T>
{
public ObservableList() : base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(nObservableCollection_CollectionChanged);
}
public event PropertyChangedEventHandler OnPropertyChanged;
void nObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (OnPropertyChanged != null)
{
OnPropertyChanged(new object[] { e.OldItems, e.NewItems }, null); // Call method to let it change the tabpages
}
}
}
Now, we have to create a helper class that helps us keeping track:
public class TabControlBind
{
public TabControlBind(TabControl tabControl)
{
// Create a new TabPageCollection and bind it to our tabcontrol
this._tabPages = new TabControl.TabPageCollection(tabControl);
}
// Fields
private ObservableList<TreeNodeItem> _treeNodeItems;
private TabControl.TabPageCollection _tabPages;
// Properties
public ObservableList<TreeNodeItem> TreeNodeItems
{
get { return _treeNodeItems; }
set
{
if (_treeNodeItems != value)
{
_treeNodeItems = value;
_treeNodeItems.OnPropertyChanged += OnPropretyChanged;
OnPropretyChanged(null, null);
}
}
}
public TabControl.TabPageCollection TabPages
{
get
{
return this._tabPages;
}
}
// Events
private void OnPropretyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender == null) // If list got set
{
// Remove existing tabpages
this._tabPages.Clear();
// Loop through all items inside the ObservableList object and add them to the Tabpage
foreach (TreeNodeItem _treeNodeItem in this._treeNodeItems)
{
TabPage tabPage = new TabPage() { Text = _treeNodeItem.Value, Tag = _treeNodeItems };
this._tabPages.Add(tabPage);
}
}
else if (sender is object[]) // If only one (or multiple) objects have been changed
{
// Get OldItems and NewItems
object[] changedItems = (object[])sender;
// Remove OldItems
if (changedItems[0] != null)
{
foreach (dynamic oldItems in (IList)changedItems[0])
{
foreach (TabPage tab in this._tabPages)
{
if (tab.Text == oldItems.Value)
{
this._tabPages.Remove(tab);
break;
}
}
}
}
// Add OldItems
if (changedItems[1] != null)
{
foreach (dynamic newItems in (IList)changedItems[1])
{
TabPage tabPage = new TabPage() { Text = newItems.Value, Tag = newItems };
this._tabPages.Add(tabPage);
}
}
}
}
}
This is a sample on how to use it:
TabControlBind tabControlBinder;
ObservableList<TreeNodeItem> treeNodeItems;
private void btnAdd_Click(object sender, EventArgs e)
{
// This will automatically update the TabControl
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test3" }));
}
private void frmMain_Load(object sender, EventArgs e)
{
// Create a new list object an add items to it
treeNodeItems = new ObservableList<TreeNodeItem>();
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test" }));
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test2" }));
// Create a new instance of the TabControlBind class, set it to our TabControl
tabControlBinder = new TabControlBind(tabControl);
tabControlBinder.TreeNodeItems = treeNodeItems;
}
I have a DataGridView filled with productinformation. The datagridview has totally 50 columns but the users don't always need all the columns, I want to help them to be able to choose which columns to show and which ones not to show.
One solution that I would like to programm is that when the user right clicks on the columns they can choose from a list that pops up choose which columns to show and which ones not to shos. Just like the image below.
How can I do that. I would really appreciate any help.
You can achieve this using the WinForms ContextMenuStrip and the Visible property of DataGridView columns.
Here is some example code that does what you want:
namespace WindowsFormsApplication4
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
BindingList<User> users = new BindingList<User>{
new User{Name = "John", Address="Home Street", Title="Mr."},
new User{Name = "Sally", Address="Home Street", Title="Mrs."}
};
contextMenuStrip1.AutoClose = true;
contextMenuStrip1.Closing += new ToolStripDropDownClosingEventHandler(contextMenuStrip1_Closing);
dataGridView1.DataSource = users;
dataGridView1.DataBindingComplete += new DataGridViewBindingCompleteEventHandler(dataGridView1_DataBindingComplete);
}
void dataGridView1_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
foreach (DataGridViewColumn gridViewColumn in this.dataGridView1.Columns)
{
ToolStripMenuItem item = new ToolStripMenuItem();
item.Name = gridViewColumn.Name;
item.Text = gridViewColumn.Name;
item.Checked = true;
item.CheckOnClick = true;
item.CheckedChanged += new EventHandler(item_CheckedChanged);
contextMenuStrip1.Items.Add(item);
}
foreach (DataGridViewColumn gridViewColumn in this.dataGridView1.Columns)
{
gridViewColumn.HeaderCell.ContextMenuStrip = contextMenuStrip1;
}
}
void item_CheckedChanged(object sender, EventArgs e)
{
ToolStripMenuItem item = sender as ToolStripMenuItem;
if (item != null)
{
dataGridView1.Columns[item.Name].Visible = item.Checked;
}
}
void contextMenuStrip1_Closing(object sender, ToolStripDropDownClosingEventArgs e)
{
if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked)
{
e.Cancel = true;
}
}
}
public class User
{
public string Name { get; set; }
public string Address { get; set; }
public string Title { get; set; }
}
}
The User class there is just so the example compiles, providing something to bind my DataGridView to.
I've also added some code that allows users to click more than one column at a time (by checking the close reason on closing and cancelling if it was an item select). This is actually a little borderline in terms of diverting from standard UI behaviour in my opinion - it is usually better to stick with standard behaviour, but I included since it is (I think) useful in this scenario.
Also, it is generally tidier to put this sort of customisation into a new control that inherits from DataGridView.