i have a question. I select a Item from a dropdown. Now i like to use this selected item to change the values of a CheckedListbox. The data with the values for the CheckedListbox are in classes. For Example:
In the DropDown I can select Machine 1 and Machine 2.
If I select Machine 1 the CheckedListbox show me Option 1, Option2 and Option 3. The Classes have different Names but the array with the options are in both Classes the same name.
I don't can't call the method with a string because a string is variable. How can i solve this?
Thanks for your help.
Form1.cs
public Form1()
{
InitializeComponent();
cbox.Items.AddRange(new ListMachinetyp().machinetype);
}
private void cbox_SelectedIndexChanged(object sender, EventArgs e)
{
string selectedItem = cbox.SelectedItem.ToString();
clb1.Items.Clear();
lbl.Text = selectedItem.ToString();
clb1.Items.AddRange(new cbox.SelectedItem.ToString().options);
}
Machine1.cs
public class Machine1
{
public string[] options = new string[] {
"Option 1",
"Option 2",
"Option 3"
};
}
Machine2.cs
public class Machine2
{
public string[] options = new string[] {
"Option 2",
"Option 5"
};
}
public Form1()
{
InitializeComponent();
List<string> machineNames = new List<string>();
machineNames.Add("Machine 1");
machineNames.Add("Machine 2");
cbox.DataSource = machineNames; // you can use any other list
}
void SetCheckedListboxSource(string selectedItem)
{
if (selectedItem == "Machine 1") // you can also say if (selectedItem == machineNames[0])
{
Machine1 mach1 = new Machine1();
checkedListBox.DataSource = null;
checkedListBox.DataSource = mach1.options;
}
else if (selectedItem == "Machine 2") // you can also say if (selectedItem == machineNames[1])
{
Machine2 mach2 = new Machine2();
checkedListBox.DataSource = null;
checkedListBox.DataSource = mach2.options;
}
}
private void cbox_SelectedIndexChanged(object sender, EventArgs e)
{
string selectedItem = cbox.Text;
SetCheckedListboxSource(selectedItem);
}
This is basic C# practice my friend. People on this site don't answer questions like this, I only answered because you seem to be new to this. Keep practicing, good luck ♥
Your post does an excellent job of describing a behavior where the list of options is filtered according to the configuration selected in the drop down list. One of many ways to achieve this outcome is to create a list of all options and then assign the DataSource of the CheckedListControl to a binding list of options containing only those that match.
A savvy coder will likely be thinking about ways to tightly associate the name of a configuration (e.g. "Machine 1") with the options it requires. One approach is to design a class that models the desired behavior:
class OptionsConfig
{
public string Device { get; set; } = string.Empty;
public string[] Options { get; set; } = new string[0];
}
You hit the nail on the head when you said that you can't call the method with a string because a string is variable but now the problem is solved. The items in the "DropDown" are OptionsConfig instances instead of strings. This is configured in the method that loads the main form. The DataSource property of the combo box to a BindingList<OptionsConfig>:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
comboBox.DataSource = _optionConfigs;
comboBox.DisplayMember = nameof(OptionsConfig.Device);
// This 'will' still compile for CheckedListBox.
// Don't be fooled when intellisense doesn't show it.
// https://stackoverflow.com/a/39076505/5438626
listBox.DataSource = _filteredOptions;
listBox.SelectionMode = SelectionMode.MultiExtended;
// Add some machines and options to test.
CreateMachineConfigs();
CreateUnfilteredOptionList();
// Init to selected item and attach handler for future changes.
onOptionsChooserChanged(this, EventArgs.Empty);
comboBox.SelectedIndexChanged += onOptionsChooserChanged;
listBox.SelectedValueChanged += (sender, e) =>
{
propertyGrid.SelectedObjects =
listBox.SelectedItems.Cast<object>().ToArray();
};
}
Now filter the options when the config changes, for example from Machine 1 to Machine 2:
private void onOptionsChooserChanged(object? sender, EventArgs e)
{
_filteredOptions.Clear();
var config = (OptionsConfig) comboBox.SelectedItem;
foreach (var option in config.Options)
{
var found = _allOptions.FirstOrDefault(_ => _.Option.Equals(option));
if(found != null)_filteredOptions.Add(found);
}
listBox.Refresh();
ActiveControl = null;
}
Consider using PropertyGrid
The CheckedListBox control may not be an ideal choice, however. In particular, it doesn't offer strong support for data binding. An alternative is using a PropertyGrid control in conjunction with a ListBox. The PropertyGrid will reconfigure itself automatically based on the selection in the `ListBox' and possibly save you a lot of work.
Testing
Create some mock machine configs for test.
BindingList<OptionsConfig> _optionConfigs = new BindingList<OptionsConfig>();
private void CreateMachineConfigs()
{
_optionConfigs.Add(new OptionsConfig
{
Device = "Machine 1",
Options = new[] { "Option 1", "Option 2", "Option 3" },
});
_optionConfigs.Add(new OptionsConfig
{
Device = "Machine 2",
Options = new[] { "Option 2", "Option 5", },
});
}
}
Create list of option classes where "the Classes have different Names".
BindingList<OptionClass> _allOptions = new BindingList<OptionClass>();
BindingList<OptionClass> _filteredOptions = new BindingList<OptionClass>();
private void CreateUnfilteredOptionList()
{
_allOptions.Add(new OptionClassB { Option = "Option 1" });
_allOptions.Add(new OptionClassA { Option = "Option 2" });
_allOptions.Add(new OptionClassA { Option = "Option 3" });
_allOptions.Add(new OptionClassA { Option = "Option 4" });
_allOptions.Add(new OptionClassB { Option = "Option 5" });
}
BindingList<OptionsConfig> _optionConfigs = new BindingList<OptionsConfig>();
Classes
Minimal examples of option classes with different names.
Option base class
abstract class OptionClass : INotifyPropertyChanged
{
[Browsable(false)]
public string Option { get; set; } = string.Empty;
[Description("Enable this option.")]
public bool IsChecked
{
get => _isChecked;
set
{
if (!Equals(_isChecked, value))
{
_isChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsChecked)));
}
}
}
private bool _isChecked = false;
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public override string ToString() => Option;
}
Fruit option class example
enum Fruit
{
Apple,
Banana,
Orange,
}
class FruitOptionClass : OptionClass
{
[Description(#"Choose a fruit.")]
public Fruit Fruit
{
get => _fruit;
set
{
if (!Equals(_fruit, value))
{
_fruit = value;
OnPropertyChanged();
}
}
}
Fruit _fruit = 0;
}
Pet option class example
enum Pet
{
Dog,
Cat,
Gerbil,
}
class PetOptionClass : OptionClass
{
[Description("Pick a species for your pet.")]
public Pet Pet
{
get => _pet;
set
{
if (!Equals(_pet, value))
{
_pet = value;
OnPropertyChanged();
}
}
}
Pet _pet = 0;
}
Related
My question is based on the ObjectListView gettingstarted code (GettingStartedTree project ) referred to in the Getting Started section of the ObjectListView sourceforge online docs.
My goal is to add checkboxes to the Title column of the TreeListView in the GettingStartedTree project.
I was able to add checkboxes simply by setting treeListView1.CheckBoxes to true and setting treeListView1.CheckedAspectName to Title (see After making changes below) as described in the instructions. Problem: However, when I run the program and click on a checkbox, a checkmark does not appear in the checkbox. I expect that a user should be able to "check" a checkbox on the UI.
Note: If I leave treeListView1.CheckBoxes set to true and set treeListView1.CheckedAspectName to null, then a checkmark does appear in the checkbox.
Am I configuring the TreeListView correctly?
Before making any changes
treeListView1:
CheckBoxes = false
CheckedAspectName = null
OLVColumn Collection, olvColumn1(Title):
Name = olvColumn1
AspectName = Title
CheckBoxes = false (Title column)
After making changes
treeListView1:
CheckBoxes = true
CheckedAspectName = Title
OLVColumn Collection, olvColumn1(Title):
Name = olvColumn1
AspectName = Title
CheckBoxes = false (Title column)
HierarchicalCheckboxes doesn't work with CheckedAspectName (or CheckedAspectGetter), although finding the documentation on this isn't easy.
Some additional information here:
Hierarchy-aware checkboxes
Just to make this clear...
To enable checkboxes for the TreeListView (or any other version of ObjectListView), just set CheckBoxes to true on the control itself.
That's all you need to do.
If you have a property on your model that indicates whether a row should be checked or not, you can automatically hook that value onto the checkboxes by setting CheckedAspectName to the name of that property. For example, if your model has an IsSelected property, then this will hook the checkbox to that property:
treeListView1.CheckedAspectName = "IsSelected";
The type of that property must be bool (or bool? if you have a tri-state check box). So, in the question, setting the CheckedAspectName to "Title" cannot work because Title is not a boolean.
Don't change any value in any column to get checkboxes to work.
If you are using hierarchical checkboxes, you can't use CheckedAspectName or anything else that eventually installs a CheckStateGetter. The docs on the Hierarchy-aware checkboxes page that Patrick mentioned explain why.
Update
After posting this answer, I came across a TreeViewList property
named HierarchicalCheckboxes that I hadn't noticed before and that I
don't see discussed/described anywhere in the ObjectListView
sourceforge docs. I now believe this is what Grammarian was referring
to in his comment under Patricks answer. In my code (in this answer),
this property is set false, so I assume I'm NOT using
HierarchicalCheckboxes. I thought hierarchical checkboxes were checkboxes in a model that has a hierarchical structure like the model in my code in this answer.
I guess/assume HierarchicalCheckboxes is
associated with the write-up on Hierarchy-aware checkboxes ,
although I'm not sure. ObjectListView is a great control. I just wish the ObjectListView sourceforge docs did a better job differentiating the explaining the collection of lists required to draw the treeview portion of a TreeListView along with adding and managing checkboxes to such a model both with with and without the use of the hierarchical checkboxes feature (enabled by the property HierarchicalCheckboxes). I guess all the pieces are in the docs, but its spread out and a bit disjointed.
End of Update
For the sake of clarity I was kind of forced to answer this myself.....
Reminder: all this is based on the gettingstartedcode described in the original question.
First, the answer to my initial problem was to clear treeview1.CheckedAspectName as in the original example code. Doing so resulted in a checkmark appearing in the checkbox (as expected) when the checkbox is clicked.
Grammarian kindly described how to programmatically check/uncheck a checkbox for (I assume) a non-hierarchical checkbox scenario, i.e., add a bool? or bool property to the model and add the property's name to the TreeListView's CheckedAspectName property.
I came to the conclusion that this won't work for hierarchical checkboxes because Grammarian said the following and loading the bool? or bool property's name into CheckedAspectName is required based on his explanation.
If you are using hierarchical checkboxes, you can't use
CheckedAspectName or anything else that eventually installs a
CheckStateGetter
So, ignoring the advice that setting the CheckedAspectName property for hierarchical checkboxes won't work, I implemented a bool? isChecked property at all levels in my hierarchical model. It works fine, i.e., I can now programmatically check a checkbox at any level in the hierarchy via the model.
Code for a simple example follows (I need to write the code to set the tri-state checkbox for parent rows based on the checkedness or uncheckedness of child rows.). The column "# To Keep" is designed to be relevant only for the level-2 items in the hierarchy
The form has only a FooTreeListView : BrightIdeasSoftware.TreeListView control on it
FooTreeListView.cs
using System;
using System.Collections.Generic;
namespace ObjectListView_TreeListView
{
class FooTreeListView : BrightIdeasSoftware.TreeListView
{
private List<Categories> categoriesList;
private readonly string[] categoryDescriptors = { "Cat A", "Cat B", "Cat C", "Cat D" };
internal List<Categories> CategoriesList { get => categoriesList; set => categoriesList = value; }
public enum CategoryEnum
{
CategoryA = 0,
CategoryB = 1,
CategoryC = 2,
CategoryD = 3
}
public FooTreeListView() : base()
{
CategoriesList = new List<Categories>();
CategoriesList.Clear();
CanExpandGetter = delegate (Object x)
{
if (x is Categories && ((Categories)x).ItemList.Count > 0)
{
return true;
}
if (x is Categories.Item && ((Categories.Item)x).ActionList.Count > 0)
{return true;
}
return false;
};
ChildrenGetter = delegate (Object x)
{
if (x is Categories)
return ((Categories)x).ItemList;
if (x is Categories.Item)
return ((Categories.Item)x).ActionList;
throw new ArgumentException("Should be Categories or Categories.Item");
};
//Load the 4 top-level categories into the tree
CategoriesList.Add(new Categories(categoryDescriptors[(int)CategoryEnum.CategoryA],false));
CategoriesList.Add(new Categories(categoryDescriptors[(int)CategoryEnum.CategoryB], false));
CategoriesList.Add(new Categories(categoryDescriptors[(int)CategoryEnum.CategoryC], false));
CategoriesList.Add(new Categories(categoryDescriptors[(int)CategoryEnum.CategoryD], false));
}
internal class Categories
{
private string action;
private bool? isChecked;
public string Action { get { return action; } set { action = value; } }
public bool? IsChecked { get => isChecked; set => isChecked = value; }
private List<Item> itemList;
internal List<Item> ItemList { get => itemList; set => itemList = value; }
public Categories(string action, bool? isChecked)
{
this.action = action;
this.isChecked = isChecked;
ItemList = new List<Item>();
}
internal class Item
{
private string action;
private bool? isChecked;
private int numberToKeep = 0;
private List<ItemAction> actionList;
public string Action { get { return action; } set { action = value; } }
public int NumberToKeep { get => numberToKeep; set => numberToKeep = value; }
public bool? IsChecked { get => isChecked; set => isChecked = value; }
internal List<ItemAction> ActionList { get => actionList; set => actionList = value; }
internal Item(string action, bool? isChecked, int numberToKeep)
{
this.action = action;
this.isChecked = isChecked;
this.NumberToKeep = numberToKeep;
ActionList = new List<ItemAction>();
}
internal class ItemAction
{
private string action;
private bool? isChecked;
public string Action { get { return action; } set { action = value; } }
public bool? IsChecked { get { return isChecked; } set { isChecked = value; } }
internal ItemAction(string action, bool? isChecked)
{
this.action = action;
this.isChecked = isChecked;
}
}
}
}
public void AddCategoryItemName(CategoryEnum category, string itemName, bool? isChecked, int numberToKeep)
{
CategoriesList[(int)category].ItemList.Add(new Categories.Item(itemName, isChecked, numberToKeep));
}
public void AddItemAction(CategoryEnum category, string itemName, string action, Boolean isChecked)
{
Categories.Item itemMatch = CategoriesList[(int)category].ItemList.Find(x => x.Action.Equals(itemName));
if (itemMatch != null)
{
itemMatch.ActionList.Add(new Categories.Item.ItemAction(action, isChecked));
}
else
{
throw new ArgumentException(String.Format("Can't find treeviewlist item '{0}'->'{1}'", categoryDescriptors[(int)category], itemName));
}
}
public void AddItemAction(CategoryEnum category, string itemName, string action)
{
Categories.Item itemMatch = CategoriesList[(int)category].ItemList.Find(x => x.Action.Equals(itemName));
if (itemMatch != null)
{
itemMatch.ActionList.Add(new Categories.Item.ItemAction(action, false));
}
else
{
throw new ArgumentException(String.Format("Can't find treeviewlist item '{0}'->'{1}'", categoryDescriptors[(int)category], itemName));
}
}
public void LoadTree()
{
Roots = CategoriesList;
ExpandAll();
}
}
}
Form1.cs
using System.Windows.Forms;
using static ObjectListView_TreeListView.FooTreeListView;
namespace ObjectListView_TreeListView
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
SuspendLayout();
xenSnapshotsTreeListView1.AddCategoryItemName(CategoryEnum.CategoryA, "Item A", true, 0);
xenSnapshotsTreeListView1.AddCategoryItemName(CategoryEnum.CategoryA, "Item B", false, 1);
xenSnapshotsTreeListView1.AddItemAction(CategoryEnum.CategoryA, "Item A", "Item A foo", true);
xenSnapshotsTreeListView1.AddItemAction(CategoryEnum.CategoryA, "Item A", "Item A bar", false);
xenSnapshotsTreeListView1.AddItemAction(CategoryEnum.CategoryA, "Item B", "Item B foo");
xenSnapshotsTreeListView1.AddItemAction(CategoryEnum.CategoryA, "Item B", "Item B bar", true);
xenSnapshotsTreeListView1.LoadTree();
ResumeLayout();
}
}
}
Form1.Designer.cs
namespace ObjectListView_TreeListView
{
partial class Form1
{
/// <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.components = new System.ComponentModel.Container();
this.xenSnapshotsTreeListView1 = new ObjectListView_TreeListView.FooTreeListView();
this.olvColumnAction = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
this.olvColumnNumbSsToKeep = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn()));
((System.ComponentModel.ISupportInitialize)(this.xenSnapshotsTreeListView1)).BeginInit();
this.SuspendLayout();
//
// xenSnapshotsTreeListView1
//
this.xenSnapshotsTreeListView1.AllColumns.Add(this.olvColumnAction);
this.xenSnapshotsTreeListView1.AllColumns.Add(this.olvColumnNumbSsToKeep);
this.xenSnapshotsTreeListView1.CellEditUseWholeCell = false;
this.xenSnapshotsTreeListView1.CheckBoxes = true;
this.xenSnapshotsTreeListView1.CheckedAspectName = "IsChecked";
this.xenSnapshotsTreeListView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
this.olvColumnAction,
this.olvColumnNumbSsToKeep});
this.xenSnapshotsTreeListView1.Cursor = System.Windows.Forms.Cursors.Default;
this.xenSnapshotsTreeListView1.Dock = System.Windows.Forms.DockStyle.Fill;
this.xenSnapshotsTreeListView1.GridLines = true;
this.xenSnapshotsTreeListView1.Location = new System.Drawing.Point(0, 0);
this.xenSnapshotsTreeListView1.MultiSelect = false;
this.xenSnapshotsTreeListView1.Name = "xenSnapshotsTreeListView1";
this.xenSnapshotsTreeListView1.ShowGroups = false;
this.xenSnapshotsTreeListView1.ShowImagesOnSubItems = true;
this.xenSnapshotsTreeListView1.Size = new System.Drawing.Size(800, 450);
this.xenSnapshotsTreeListView1.TabIndex = 0;
this.xenSnapshotsTreeListView1.UseAlternatingBackColors = true;
this.xenSnapshotsTreeListView1.UseCompatibleStateImageBehavior = false;
this.xenSnapshotsTreeListView1.View = System.Windows.Forms.View.Details;
this.xenSnapshotsTreeListView1.VirtualMode = true;
//
// olvColumnAction
//
this.olvColumnAction.AspectName = "Action";
this.olvColumnAction.Text = "Action";
this.olvColumnAction.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.olvColumnAction.Width = 200;
//
// olvColumnNumbSsToKeep
//
this.olvColumnNumbSsToKeep.AspectName = "NumberToKeep";
this.olvColumnNumbSsToKeep.Text = "# To Keep";
this.olvColumnNumbSsToKeep.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
this.olvColumnNumbSsToKeep.Width = 65;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.xenSnapshotsTreeListView1);
this.Name = "Form1";
this.Text = "Form1";
((System.ComponentModel.ISupportInitialize)(this.xenSnapshotsTreeListView1)).EndInit();
this.ResumeLayout(false);
}
#endregion
private FooTreeListView xenSnapshotsTreeListView1;
private BrightIdeasSoftware.OLVColumn olvColumnAction;
private BrightIdeasSoftware.OLVColumn olvColumnNumbSsToKeep;
}
}
So my program is going to end up being fairly large and I don't want a whole lot of code that could just be shortened. Here is one instance I am looking for some tips on:
private void bookComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
string books = null;
// sets books to the clicked item
books = bookComboBox.SelectedItem.ToString();
selectedPictureBox.Visible = true;
// Loads string to list box and image to selectedPictureBox when programming is selected
if (books == "Programming")
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile("programming.png");
bookListBox.Items.Add("Visual Basic");
bookListBox.Items.Add("Java");
bookListBox.Items.Add("C#");
}
// Loads string to list box and image to selectedPictureBox when Networking is selected
else if (books == "Networking")
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile("networking.png");
bookListBox.Items.Add("LAN Networks");
bookListBox.Items.Add("Windows Networking");
bookListBox.Items.Add("More About Networking");
}
// Loads string to list box and image to selectedPictureBox when Web is selected
else if (books == "Web")
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile("html.png");
bookListBox.Items.Add("Web Programming");
bookListBox.Items.Add("JavaScript");
bookListBox.Items.Add("ASP");
}
}
The code works fine but I was just hoping to get some tips on shortening this code if necessary, any input is appreciated.
Assuming you can use C# 7's new Tuples:
private Dictionary<string, (string image, List<string> books)> books = new Dictionary<string, (string image, List<string> books)>
{
{ "Programming", ("programming.png", new List<string> { "Visual Basic", "Java", "C#"} ) },
{ "Networking", ("networking.png", new List<string> {"LAN Networks", "Windows Networking", "More About Networking"}) },
{ "Web", ("html.png", new List<string> {"Web Programming", "Javascript", "ASP"}) }
};
private void bookComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
// sets books to the clicked item
string book = bookComboBox.SelectedItem.ToString();
selectedPictureBox.Visible = true;
if (books.Keys.Contains(book))
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile(books[book].image);
foreach(var b in books[book].books)
{
bookListBox.Items.Add(b);
}
}
}
But a class is likely even better:
public class BookGroup
{
public string ImagePath {get;set;}
public List<string> Books {get;}
public BookGroup(string imagePath, params string[] books)
{
ImagePath = imagePath;
Books = new List<string>(books.Length);
Books.AddRange(books);
}
}
Which isn't all that different to use right now, but it formalizes some things that might make this code easier to work with down the road, and it's possible if you can't use Tuples yet. I might also have a Book class by itself, just for fun, but for now:
private Dictionary<string, BookGroup> books = new Dictionary<string, BookGroup>
{
{ "Programming", new BookGroup("programming.png", "Visual Basic", "Java", "C#")},
{ "Networking", new BookGroup("networking.png","LAN Networks", "Windows Networking", "More About Networking") },
{ "Web", new BookGroup("html.png", "Web Programming", "Javascript", "ASP") }
};
private void bookComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
// sets books to the clicked item
string book = bookComboBox.SelectedItem.ToString();
selectedPictureBox.Visible = true;
if (books.Keys.Contains(book))
{
bookListBox.Items.Clear();
BookGroup bg = books[book];
selectedPictureBox.Image = Image.FromFile(bg.ImagePath);
foreach(var b in bg.Books)
{
bookListBox.Items.Add(b);
}
}
}
Regardless, I'd definitely have a way to load these from a text file... likely a csv, or maybe even a small in-process database, so that I could update this listing without having to recompile or distribute new program code. And, with that in mind, in order to fit this data in a single structure in a single file, I'd likely also repeat the image and type with each book, so that my csv data looks like this:
Topic,Image,Title
Programming,programming.png,"Visual Basic"
Programming,programming.png,"Java"
Programming,programming.png,"C#"
Networking,networking.png,"LAN Networks"
Networking,networking.png,"Windows Networking"
Networking,networking.png,"More About Networking"
Web,html.png,"Web Programming"
Web,html.png,"Javascript"
Web,html.png,"ASP"
That changes the whole character of the code. I'm a bit biased, but I'd likely use this CSV Parser, and again assuming Tuples I'd produce something like this:
private List<(string Topic, string ImagePath, string Title)> books;
//In the form load code:
books = EasyCSV.FromFile("bookData.csv").Select(b => (b[0], b[1], b[2])).ToList();
//and finally, in the original selectindexchanged method:
private void bookComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
string topic = bookComboBox.SelectedItem.ToString();
selectedPictureBox.Visible = true;
var items = books.Where(b => b.Topic == topic).ToArray();
if(items.Length > 0)
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile(items[0].ImagePath);
bookListBox.Items.AddRange(items);
}
}
Make objects and use databindings.
public class Book
{
public BookType BookType { get; set; }
public string Name { get; set; }
public string Image { get; set; }
}
public enum BookType
{
Programming,
Networking,
Web,
}
public partial class Form1 : Form
{
private readonly List<Book> _books = new List<Book>
{
new Book { Image = "programming.png", BookType = BookType.Programming, Name = "VB" },
new Book { Image = "programming.png", BookType = BookType.Programming, Name = "Java" },
new Book { Image = "programming.png", BookType = BookType.Programming, Name = "C#" },
new Book { Image = "networking.png", BookType = BookType.Networking, Name = "LAN Networks" },
new Book { Image = "networking.png", BookType = BookType.Networking, Name = "Windows Networking" },
new Book { Image = "networking.png", BookType = BookType.Networking, Name = "More About Networking" },
new Book { Image = "html.png", BookType = BookType.Web, Name = "Web Programming" },
new Book { Image = "html.png", BookType = BookType.Web, Name = "Javascript" },
new Book { Image = "html.png", BookType = BookType.Web, Name = "ASP" },
};
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var bookTypes = _books.GroupBy(b => b.BookType).Select(g => g.Key).ToList();
this.cboBookTypes.DataSource = bookTypes;
}
private void cboBookTypes_SelectedIndexChanged(object sender, EventArgs e)
{
var bookType = (BookType)this.cboBookTypes.SelectedItem;
var books = _books.Where(b => b.BookType == bookType).ToList();
var img = books.First().Image;
this.imgBook.Image = Image.FromFile(img);
this.lstBooks.DataSource = books;
this.lstBooks.DisplayMember = "Name";
}
}
If you are talking about the length of the code, I would suggest using switch-case-break-default construct
Switch the books variable.
This wont improve the performance though
I think you should create a class that represents book category. Then, you could simply iterate through all the category lists and extract the necessary information, like this:
string books = null;
books = bookComboBox.SelectedItem.ToString();
selectedPictureBox.Visible = true;
for (int i = 0; i < categories.Count; i++) {
if (books == categories[i].Name) {
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile(categories[i].ImagePath);
for (int j = 0; j < categories[i].Items.Count; j++) {
bookListBox.Items.Add(categories[i].Items[j]);
}
}
}
I would suggest to keep all the data in a configuration object and then iterate through that data when performing checks and assignments.
I would create a separate class to hold data for each book: name, picture file name and check box items string array.
Then I would create a list of that object and assign all the data manually on form initialization.
After that, in SelectedIndexChanged event handler, I would iterate (for loop) on each item and check if the book name matched. If it did, then I would use data from that object and then break; the loop.
I do not have visual studio, so giving you the points/suggestions to improve on.
switch should be preferred over if-elseif.
bookListBox.Items.Clear(); and selectedPictureBox.Image out of if block. Use a variable to set the image file name.
Create a class to represent a book list:
public class BookList
{
public string ImageName { get; set; }
public List<string> Items { get;set; }
}
Then create a dictionary to hold these items:
Dictionary<string, BookList> bookLists = new Dictionary<string, BookList>
{
{
"Programming",
new BookList { ImageName = "programming.png", Items = new List<string> { ... } }
}
...
};
Then modify your if statements to:
if (bookLists.ContainsKey(books))
{
bookListBox.Items.Clear();
selectedPictureBox.Image = Image.FromFile(bookLists[books].ImageName);
foreach (var b in bookLists[books].Items)
{
bookListBox.Items.Add(b);
}
}
I am facing a problem. I have set of some enum in my app. Like
public enum EnmSection
{
Section1,
Section2,
Section3
}
public enum Section1
{
TestA,
TestB
}
public enum Section2
{
Test1,
Test2
}
EnmSection is main enum which contains the other enum(as string) which are declared below it. Now i have to fill the values of EnmSection in a drop-down.I have done it.
Like this...
drpSectionType.DataSource = Enum.GetNames(typeof(EnmSection));
drpSectionType.DataBind();
Now my drop-down has values: Section1,Section2,Section3
Problem is:
I have another drop-down drpSubSection. Now i want to fill this drop-down whatever i have selected in the drpSectionType.
for ex If I selected Section1 in drpSectionType then drpSubsection should contain the value
TestA,TestB. Like this:
protected void drpSectionType_SelectedIndexChanged(object sender, EventArgs e)
{
string strType = drpSectionType.SelectedValue;
drpSubsection.DataSource = Enum.GetNames(typeof());
drpSubsection.DataBind();
}
Here typeof() is expecting the enum.But i am getting selected value as string. How can i achieve this functionality.
Thanks
What if you reference an assembly that contains another enum with a value named Section1?
You'll just have to try all the enums you care about, one at a time, and see which one works. You'll probably want to use Enum.TryParse.
Something like this might work, but you have to do some exception handling:
protected void drpSectionType_SelectedIndexChanged(object sender, EventArgs e)
{
string strType = drpSectionType.SelectedValue;
EnmSection section = (EnmSection)Enum.Parse(typeof(EnmSection), strType);
drpSubsection.DataSource = Enum.GetNames(typeof(section));
drpSubsection.DataBind();
}
This might be a bit over the top but it would work if you bind bind Arrays of IEnumItem to your drop down and set it up to show their display text.
public interface IEnumBase
{
IEnumItem[] Items { get; }
}
public interface IEnumItem : IEnumBase
{
string DisplayText { get; }
}
public class EnumItem : IEnumItem
{
public string DisplayText { get; set; }
public IEnumItem[] Items { get; set; }
}
public class EnmSections : IEnumBase
{
public IEnumItem[] Items { get; private set; }
public EnmSections()
{
Items = new IEnumItem[]
{
new EnumItem
{
DisplayText = "Section1",
Items = new IEnumItem[]
{
new EnumItem { DisplayText = "TestA" },
new EnumItem { DisplayText = "TestB" }
}
},
new EnumItem
{
DisplayText = "Section2",
Items = new IEnumItem[]
{
new EnumItem { DisplayText = "Test1" },
new EnumItem { DisplayText = "Test2" }
}
}
};
}
}
drpSubsection.DataSource = Enum.GetNames(Type.GetType("Your.Namespace." + strType));
If the enums are in another assembly, (i.e. they're not in mscorlib or the current assembly) you'll need to provide the AssemblyQualifiedName. The easiest way to get this will be to look at typeof(Section1).AssemblyQualifiedName, then modify your code to include all the necessary parts. The code will look something like this when you're done:
drpSubsection.DataSource = Enum.GetNames(Type.GetType("Your.Namespace." + strType + ", MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089"));
I have a situation that is pretty simple, and I'd like to know the ideal way to do it.
I have a combo box. Each line of the combo box corresponds to a particular strategy object.
What is the proper way to map the combo box lines to the strategy object.
The way I was doing it seems overly complicated, and I'm pretty much guaranteed there is a simple standard way to do this.
Thank you.
EDIT:
I had the data in a Dictionary, where the string was the text for the combobox, and the object was the strategy... But this isn't ordered... And I just know there is some extremely simple way to do it.
SOLUTION:
I used this solution, not feeling comfortable putting presentation logic in the data classes:
private partial class HtmlTransformState : AbstractHtmlEditFormState
{
private Dictionary<string, ITransformStrategy> strategies = new Dictionary<string, ITransformStrategy>()
{
{ "Simple URL", new TransformStrategy<SimpleUrlCodeExtractor>() },
{ "Overview", new TransformStrategy<OverviewCodeExtractor>() },
{ "Video List", new TransformStrategy<VideoListCodeExtractor>() },
{ "Video List No MbORKb", new TransformStrategy<VideoListNoMBOrKBAndNoLinksAllowedCodeExtractor>() },
{ "Blue Mountain 2007", new TransformStrategy<BlueMountain2007CodeExtractor>() },
{ "Four Gates", new TransformStrategy<FourGatesCodeExtractor>() },
{ "General", new TransformStrategy<GeneralCodeExtractor>() }
};
public override void DrawForm()
{
// ...
ParentForm.cmboTransformStrategy.DataSource = new BindingSource(strategies, null);
ParentForm.cmboTransformStrategy.DisplayMember = "Key";
ParentForm.cmboTransformStrategy.ValueMember = "Value";
}
public override IEnumerable<string> ProcessHtml(string urlPath)
{
ITransformStrategy transformStrategy = (ITransformStrategy)ParentForm.cmboTransformStrategy.SelectedValue;
// Do some stuff with 'transformStrategy'
}
}
Do you mean something like the following?
public class Strategy
{
private string _name = "default";
public string Name
{
get { return _name; }
set { _name = value; }
}
public Strategy(string name)
{
_name = name;
}
}
Then in form load (you need to have a combo box on that form):
private void Form1_Load(object sender, EventArgs e)
{
List<Strategy> ls = new List<Strategy>();
ls.Add(new Strategy("First"));
ls.Add(new Strategy("Second"));
ls.Add(new Strategy("Third"));
comboBox1.DataSource = ls;
comboBox1.DisplayMember = "Name";
}
Override ToString for your strategy object. After that you can insert your strategy objects directly in the combo box.
public class StrategyObject
{
public override string ToString()
{
return "return the text to display";
}
}
StrategyObject selectedStratObj = comboBox1.SelectedItem as StrategyObject;
I would use the SelectedIndexChanged event on the combobox and select the corresponding dictionary entry
found that, Bind a Dictionary to a ComboBox see below for a working example(at least on the original vb.net code that I wrote)
Vb.net converted into C#, you will have to manage the handle yourself
public class Form1
{
private Dictionary<int, myDic> dict = new Dictionary<int, myDic>();
private void // ERROR: Handles clauses are not supported in C#
ComboBox1_SelectedIndexChanged(System.Object sender, System.EventArgs e)
{
KeyValuePair<int, myDic> curItem = (KeyValuePair<int, myDic>)ComboBox1.SelectedItem;
MessageBox.Show(curItem.Value.myvalue);
}
private void // ERROR: Handles clauses are not supported in C#
Form1_Load(object sender, System.EventArgs e)
{
myDic d = default(myDic);
for (int i = 0; i <= 10; i++) {
d = new myDic();
d.myKey = i.ToString;
d.myvalue = Strings.Chr(65 + i);
dict.Add(d.GetHashCode, d);
}
ComboBox1.DataSource = new BindingSource(dict, null);
ComboBox1.DisplayMember = "value";
ComboBox1.ValueMember = "Key";
}
}
class myDic
{
public string myKey;
public string myvalue;
public override string tostring()
{
return myvalue;
}
}
Here's one of my finest innovations. :) I'm really proud of this little one.
public class Stringable<T>
{
private T _obj;
private Func<T, string> _convertFn;
public Stringable(T obj, Func<T, string> convertFn)
{
_obj = obj;
_convertFn = convertFn;
}
public T GetObj() { return _obj; }
public override string ToString() { return _convertFn(_obj); }
}
This generic class adds ToString() to any class (even a black-box class) and you can define its behavior inside the lambda. Imagine you have a class Person with properties FirstName and LastName. Here's how you would use it to populate Combo box.
_cboPersons.Items.Add(new Stringable<Person>(person,o=>string.Format("{0}, {1}", o.LastName, o.FirstName)));
Then, when combo box item is selected just use this to get the original object from your combo
Person person=(_cboPersons.SelectedItem as Stringable<Person>).GetObj() // Get's person object.
I have a grid, and I'm setting the DataSource to a List<IListItem>. What I want is to have the list bind to the underlying type, and disply those properties, rather than the properties defined in IListItem. So:
public interface IListItem
{
string Id;
string Name;
}
public class User : IListItem
{
string Id { get; set; };
string Name { get; set; };
string UserSpecificField { get; set; };
}
public class Location : IListItem
{
string Id { get; set; };
string Name { get; set; };
string LocationSpecificField { get; set; };
}
How do I bind to a grid so that if my List<IListItem> contains users I will see the user-specific field? Edit: Note that any given list I want to bind to the Datagrid will be comprised of a single underlying type.
Data-binding to lists follows the following strategy:
does the data-source implement IListSource? if so, goto 2 with the result of GetList()
does the data-source implement IList? if not, throw an error; list expected
does the data-source implement ITypedList? if so use this for metadata (exit)
does the data-source have a non-object indexer, public Foo this[int index] (for some Foo)? if so, use typeof(Foo) for metadata
is there anything in the list? if so, use the first item (list[0]) for metadata
no metadata available
List<IListItem> falls into "4" above, since it has a typed indexer of type IListItem - and so it will get the metadata via TypeDescriptor.GetProperties(typeof(IListItem)).
So now, you have three options:
write a TypeDescriptionProvider that returns the properties for IListItem - I'm not sure this is feasible since you can't possibly know what the concrete type is given just IListItem
use the correctly typed list (List<User> etc) - simply as a simple way of getting an IList with a non-object indexer
write an ITypedList wrapper (lots of work)
use something like ArrayList (i.e. no public non-object indexer) - very hacky!
My preference is for using the correct type of List<>... here's an AutoCast method that does this for you without having to know the types (with sample usage);
Note that this only works for homogeneous data (i.e. all the objects are the same), and it requires at least one object in the list to infer the type...
// infers the correct list type from the contents
static IList AutoCast(this IList list) {
if (list == null) throw new ArgumentNullException("list");
if (list.Count == 0) throw new InvalidOperationException(
"Cannot AutoCast an empty list");
Type type = list[0].GetType();
IList result = (IList) Activator.CreateInstance(typeof(List<>)
.MakeGenericType(type), list.Count);
foreach (object obj in list) result.Add(obj);
return result;
}
// usage
[STAThread]
static void Main() {
Application.EnableVisualStyles();
List<IListItem> data = new List<IListItem> {
new User { Id = "1", Name = "abc", UserSpecificField = "def"},
new User { Id = "2", Name = "ghi", UserSpecificField = "jkl"},
};
ShowData(data, "Before change - no UserSpecifiedField");
ShowData(data.AutoCast(), "After change - has UserSpecifiedField");
}
static void ShowData(object dataSource, string caption) {
Application.Run(new Form {
Text = caption,
Controls = {
new DataGridView {
Dock = DockStyle.Fill,
DataSource = dataSource,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false
}
}
});
}
As long as you know for sure that the members of the List<IListItem> are all going to be of the same derived type, then here's how to do it, with the "Works on my machine" seal of approval.
First, download BindingListView, which will let you bind generic lists to your DataGridViews.
For this example, I just made a simple form with a DataGridView and randomly either called code to load a list of Users or Locations in Form1_Load().
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Equin.ApplicationFramework;
namespace DGVTest
{
public interface IListItem
{
string Id { get; }
string Name { get; }
}
public class User : IListItem
{
public string UserSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Location : IListItem
{
public string LocationSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void InitColumns(bool useUsers)
{
if (dataGridView1.ColumnCount > 0)
{
return;
}
DataGridViewCellStyle gridViewCellStyle = new DataGridViewCellStyle();
DataGridViewTextBoxColumn IDColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn NameColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn DerivedSpecificColumn = new DataGridViewTextBoxColumn();
IDColumn.DataPropertyName = "ID";
IDColumn.HeaderText = "ID";
IDColumn.Name = "IDColumn";
NameColumn.DataPropertyName = "Name";
NameColumn.HeaderText = "Name";
NameColumn.Name = "NameColumn";
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField";
DerivedSpecificColumn.HeaderText = "Derived Specific";
DerivedSpecificColumn.Name = "DerivedSpecificColumn";
dataGridView1.Columns.AddRange(
new DataGridViewColumn[]
{
IDColumn,
NameColumn,
DerivedSpecificColumn
});
gridViewCellStyle.SelectionBackColor = Color.LightGray;
gridViewCellStyle.SelectionForeColor = Color.Black;
dataGridView1.RowsDefaultCellStyle = gridViewCellStyle;
}
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = false;
Random rand = new Random();
bool useUsers = rand.Next(0, 2) == 0;
InitColumns(useUsers);
if(useUsers)
{
TestUsers();
}
else
{
TestLocations();
}
}
private void TestUsers()
{
List<IListItem> items =
new List<IListItem>
{
new User {Id = "1", Name = "User1", UserSpecificField = "Test User 1"},
new User {Id = "2", Name = "User2", UserSpecificField = "Test User 2"},
new User {Id = "3", Name = "User3", UserSpecificField = "Test User 3"},
new User {Id = "4", Name = "User4", UserSpecificField = "Test User 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
}
private void TestLocations()
{
List<IListItem> items =
new List<IListItem>
{
new Location {Id = "1", Name = "Location1", LocationSpecificField = "Test Location 1"},
new Location {Id = "2", Name = "Location2", LocationSpecificField = "Test Location 2"},
new Location {Id = "3", Name = "Location3", LocationSpecificField = "Test Location 3"},
new Location {Id = "4", Name = "Location4", LocationSpecificField = "Test Location 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
}
}
}
The important lines of code are these:
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField"; // obviously need to bind to the derived field
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
dataGridView1.AutoGenerateColumns = false; // Be specific about which columns to show
and the most important are these:
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
If all items in the list are known to be of the certain derived type, just call ConvertAll to cast them to that type.
You'll need to use a Grid template column for this. Inside the template field you'll need to check what the type of the object is and then get the correct property - I recommend creating a method in your code-behind which takes care of this. Thus:
<asp:TemplateField HeaderText="PolymorphicField">
<ItemTemplate>
<%#GetUserSpecificProperty(Container.DataItem)%>
</ItemTemplate>
</asp:TemplateField>
In your code-behind:
protected string GetUserSpecificProperty(IListItem obj) {
if (obj is User) {
return ((User) obj).UserSpecificField
} else if (obj is Location) {
return ((Location obj).LocationSpecificField;
} else {
return "";
}
}
I tried projections, and I tried using Convert.ChangeType to get a list of the underlying type, but the DataGrid wouldn't display the fields. I finally settled on creating static methods in each type to return the headers, instance methods to return the display fields (as a list of string) and put them together into a DataTable, and then bind to that. Reasonably clean, and it maintains the separation I wanted between the data types and the display.
Here's the code I use to create the table:
DataTable GetConflictTable()
{
Type type = _conflictEnumerator.Current[0].GetType();
List<string> headers = null;
foreach (var mi in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
if (mi.Name == "GetHeaders")
{
headers = mi.Invoke(null, null) as List<string>;
break;
}
}
var table = new DataTable();
if (headers != null)
{
foreach (var h in headers)
{
table.Columns.Add(h);
}
foreach (var c in _conflictEnumerator.Current)
{
table.Rows.Add(c.GetFieldsForDisplay());
}
}
return table;
}
When you use autogeneratecolumns it doesnt automatically do this for you?
My suggestion would be to dynamically create the columns in the grid for the extra properties and create either a function in IListItem that gives a list of available columns - or use object inspection to identify the columns available for the type.
The GUI would then be much more generic, and you would not have as much UI control over the extra columns - but they would be dynamic.
Non-checked/compiled 'psuedo code';
public interface IListItem
{
IList<string> ExtraProperties;
... your old code.
}
public class User : IListItem
{
.. your old code
public IList<string> ExtraProperties { return new List { "UserSpecificField" } }
}
and in form loading
foreach(string columnName in firstListItem.ExtraProperties)
{
dataGridView.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = columnName, HeaderText = columnName );
}
If you are willing to use a ListView based solution, the data-bindable version ObjectListView will let you do this. It reads the exposed properties of the DataSource and creates columns to show each property. You can combine it with BindingListView.
It also looks nicer than a grid :)