I want to add submenu into the main menu in runtime. I have looked at other post
Adding to strip menu at run time but I don't understand what I'm missing here because it only populates one item, but I have three xml files in the folder. Below is the code. testsuite paramenter contains xml files.
public void LoadTestSuiteMenuStrip(string[] testsuite)
{
try
{
foreach (var temp in testsuite)
{
int max = temp.Length-1;
while (temp[max] != '\\')
max--;
max++;
//remove the folder path and take only xml file name
string name = temp.Substring(max, temp.Length - max);
ToolStripMenuItem subItem = new ToolStripMenuItem(name);
//subItem.DisplayStyle = ToolStripItemDisplayStyle.Text;
//subItem .Text = name;
//subItem .Name = name;
//subItem.Tag = name;
subItem.Click += new EventHandler(testSuiteToolstrip_Click);
testsuiteToolStripMenuItem.DropDownItems.Add(subItem);
}
}
catch (Exception error)
{
}
}
thanks
Make sure that you access GUI from main thread. If you want to add items from other thread, instead of
LoadTestSuiteMenuStrip(args);
use
Invoke(new Action<string[]>(LoadTestSuiteMenuStrip), new object[] { args });
Related
I have a ToolStripMenuItem called myMenu. How can I access this like so:
/* Normally, I would do: */
this.myMenu... etc.
/* But how do I access it like this: */
String name = myMenu;
this.name...
This is because I am dynamically generating ToolStripMenuItems from an XML file and need to reference MenuItems by their dynamically generated names.
Use the Control.ControlCollection.Find method.
Try this:
this.Controls.Find()
string name = "the_name_you_know";
Control ctn = this.Controls[name];
ctn.Text = "Example...";
Assuming you have the menuStrip object and the menu is only one level deep, use:
ToolStripMenuItem item = menuStrip.Items
.OfType<ToolStripMenuItem>()
.SelectMany(it => it.DropDownItems.OfType<ToolStripMenuItem>())
.SingleOrDefault(n => n.Name == "MyMenu");
For deeper menu levels add more SelectMany operators in the statement.
if you want to search all menu items in the strip then use
ToolStripMenuItem item = menuStrip.Items
.Find("MyMenu",true)
.OfType<ToolStripMenuItem>()
.Single();
However, make sure each menu has a different name to avoid exception thrown by key duplicates.
To avoid exceptions you could use FirstOrDefault instead of SingleOrDefault / Single, or just return a sequence if you might have Name duplicates.
Control GetControlByName(string Name)
{
foreach(Control c in this.Controls)
if(c.Name == Name)
return c;
return null;
}
Disregard this, I reinvent wheels.
Using the same approach of Philip Wallace, we can do like this:
public Control GetControlByName(Control ParentCntl, string NameToSearch)
{
if (ParentCntl.Name == NameToSearch)
return ParentCntl;
foreach (Control ChildCntl in ParentCntl.Controls)
{
Control ResultCntl = GetControlByName(ChildCntl, NameToSearch);
if (ResultCntl != null)
return ResultCntl;
}
return null;
}
Example:
public void doSomething()
{
TextBox myTextBox = (TextBox) this.GetControlByName(this, "mytextboxname");
myTextBox.Text = "Hello!";
}
I hope it help! :)
this.Controls.Find(name, searchAllChildren) doesn't find ToolStripItem because ToolStripItem is not a Control
using SWF = System.Windows.Forms;
using NUF = NUnit.Framework;
namespace workshop.findControlTest {
[NUF.TestFixture]
public class FormTest {
[NUF.Test]public void Find_menu() {
// == prepare ==
var fileTool = new SWF.ToolStripMenuItem();
fileTool.Name = "fileTool";
fileTool.Text = "File";
var menuStrip = new SWF.MenuStrip();
menuStrip.Items.Add(fileTool);
var form = new SWF.Form();
form.Controls.Add(menuStrip);
// == execute ==
var ctrl = form.Controls.Find("fileTool", true);
// == not found! ==
NUF.Assert.That(ctrl.Length, NUF.Is.EqualTo(0));
}
}
}
One of the best way is a single row of code like this:
In this example we search all PictureBox by name in a form
PictureBox[] picSample =
(PictureBox)this.Controls.Find(PIC_SAMPLE_NAME, true);
Most important is the second paramenter of find.
if you are certain that the control name exists you can directly use it:
PictureBox picSample =
(PictureBox)this.Controls.Find(PIC_SAMPLE_NAME, true)[0];
You can use find function in your Form class. If you want to cast (Label) ,(TextView) ... etc, in this way you can use special features of objects. It will be return Label object.
(Label)this.Controls.Find(name,true)[0];
name: item name of searched item in the form
true: Search all Children boolean value
this.Controls["name"];
This is the actual code that is ran:
public virtual Control this[string key]
{
get
{
if (!string.IsNullOrEmpty(key))
{
int index = this.IndexOfKey(key);
if (this.IsValidIndex(index))
{
return this[index];
}
}
return null;
}
}
vs:
public Control[] Find(string key, bool searchAllChildren)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key", SR.GetString("FindKeyMayNotBeEmptyOrNull"));
}
ArrayList list = this.FindInternal(key, searchAllChildren, this, new ArrayList());
Control[] array = new Control[list.Count];
list.CopyTo(array, 0);
return array;
}
private ArrayList FindInternal(string key, bool searchAllChildren, Control.ControlCollection controlsToLookIn, ArrayList foundControls)
{
if ((controlsToLookIn == null) || (foundControls == null))
{
return null;
}
try
{
for (int i = 0; i < controlsToLookIn.Count; i++)
{
if ((controlsToLookIn[i] != null) && WindowsFormsUtils.SafeCompareStrings(controlsToLookIn[i].Name, key, true))
{
foundControls.Add(controlsToLookIn[i]);
}
}
if (!searchAllChildren)
{
return foundControls;
}
for (int j = 0; j < controlsToLookIn.Count; j++)
{
if (((controlsToLookIn[j] != null) && (controlsToLookIn[j].Controls != null)) && (controlsToLookIn[j].Controls.Count > 0))
{
foundControls = this.FindInternal(key, searchAllChildren, controlsToLookIn[j].Controls, foundControls);
}
}
}
catch (Exception exception)
{
if (ClientUtils.IsSecurityOrCriticalException(exception))
{
throw;
}
}
return foundControls;
}
Assuming you have Windows.Form Form1 as the parent form which owns the menu you've created. One of the form's attributes is named .Menu. If the menu was created programmatically, it should be the same, and it would be recognized as a menu and placed in the Menu attribute of the Form.
In this case, I had a main menu called File. A sub menu, called a MenuItem under File contained the tag Open and was named menu_File_Open. The following worked. Assuming you
// So you don't have to fully reference the objects.
using System.Windows.Forms;
// More stuff before the real code line, but irrelevant to this discussion.
MenuItem my_menuItem = (MenuItem)Form1.Menu.MenuItems["menu_File_Open"];
// Now you can do what you like with my_menuItem;
Since you're generating them dynamically, keep a map between a string and the menu item, that will allow fast retrieval.
// in class scope
private readonly Dictionary<string, ToolStripMenuItem> _menuItemsByName = new Dictionary<string, ToolStripMenuItem>();
// in your method creating items
ToolStripMenuItem createdItem = ...
_menuItemsByName.Add("<name here>", createdItem);
// to access it
ToolStripMenuItem menuItem = _menuItemsByName["<name here>"];
Have a look at the ToolStrip.Items collection. It even has a find method available.
You can do the following:
private ToolStripMenuItem getToolStripMenuItemByName(string nameParam)
{
foreach (Control ctn in this.Controls)
{
if (ctn is ToolStripMenuItem)
{
if (ctn.Name = nameParam)
{
return ctn;
}
}
}
return null;
}
A simple solution would be to iterate through the Controls list in a foreach loop. Something like this:
foreach (Control child in Controls)
{
// Code that executes for each control.
}
So now you have your iterator, child, which is of type Control. Now do what you will with that, personally I found this in a project I did a while ago in which it added an event for this control, like this:
child.MouseDown += new MouseEventHandler(dragDown);
I'm pretty new to C# and Windows Form programming. I'd like to create a simple Windows Form which has a ListView initially populated with some items. Then, if I double click on an item I'd like to show some other sub-items. Apparently there will be a '...' item at the top which take me to the parent item. Can someone help me writing this simple windows form?
p.s. I'm not very familiar with C# Windows Form application architecture (Form.Designer.cs, Form1, Program.cs, etc)
If you want to use the ListView you could populate it when you double Click a Directory.
What you need is
A double Click event
DirectoryInfo
FileInfo
When programming a Windows Forms application you should consider dividing your business logic from your view logic keywords here are Model view controller or Model View Presenter.
Lets begin
I would create a DirectoryController class which handles some of your logic.
Your Forms class can handle the view logic then for example the double click event.
You could create a method which generates an ImageList so that you can use it in your ListView to have the correct Image for your File or Directory
private static ImageList GetFolderImageList()
{
Image folderImg = Image.FromFile(#"C:\Windows\WinSxS\amd64_microsoft-windows-printing-fdprint_31bf3856ad364e35_6.3.9600.17415_none_493b0b9e590044a1\folder.ico");
ImageList imgList = new ImageList();
imgList.Images.Add(folderImg);
return imgList;
}
I used an Image from the Windows directory just so that this will initially work. But you can use an Image you want to show when having a directory in your ListView. By the way this will only demonstrate how to handle directories and not files, but from this point you should be able to handle files by yourself.
After you created the ImageList you can create Initialize the ListView.
private void InitializeListView(ImageList imgList)
{
lvExplorer.DoubleClick += new System.EventHandler(this.lvExplorer_DoubleClick);
lvExplorer.SmallImageList = imgList;
lvExplorer.LargeImageList = imgList;
}
My ListView is named lvExplorer and I added it already to the Windows Form in the designer. I used the Dockstyle to fill the whole form.
With this two functions you should be able to Initialize the ListView.
Now you need to populate the ListView.
private void ShowDirectoriesInListView(string path)
{
DirectoryInfo info = new DirectoryInfo(path);
DirectoryInfo parent = info.Parent;
if (parent != null)
{
lvExplorer.Items.Add(new System.Windows.Forms.ListViewItem("...", 0));
}
foreach (DirectoryInfo dInfo in info.GetDirectories())
{
lvExplorer.Items.Add(new System.Windows.Forms.ListViewItem(dInfo.Name, 0));
}
}
With the ShowDirectoriesInListView method you can add your Directories to the ListView. The path parameter is the directory you want to show.
If the directory has a parent we add the "..." directory. A root directory normally has no parent so we won't add it then.
Now lets get to the point how we get the directory that we passed into the ShowDirectoriesInListView method.
As mentioned before we want to split view from business logic
So I created a controller class
class DirectoryController
{
private string _currentDirectory;
public DirectoryController(string beginDirectory)
{
_currentDirectory = beginDirectory;
}
public string AddDirectoryAndGetPath(string path)
{
if (path.Equals("..."))
{
int lastIndex = _currentDirectory.Length;
if (_currentDirectory.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
lastIndex = _currentDirectory.LastIndexOf(Path.DirectorySeparatorChar);
_currentDirectory = _currentDirectory.Remove(lastIndex);
}
lastIndex = _currentDirectory.LastIndexOf(Path.DirectorySeparatorChar) + 1;
_currentDirectory = _currentDirectory.Remove(lastIndex);
}
else
{
_currentDirectory = string.Format("{0}{1}{2}", _currentDirectory, path, Path.DirectorySeparatorChar);
}
return _currentDirectory;
}
}
The most important function of this class is the AddDirectoryAndGetPath method.
If the method gets a string with the contents "..." we remove the last directory name from our current directory otherwise we will add it.
Now coming back to the double click event.
You did set up a Event handler in your InitializeListView method
The method which is called should have the same name as the method you passed as argument into the constructor of the EventHandler.
private void lvExplorer_DoubleClick(object sender, EventArgs e)
{
string path = lvExplorer.SelectedItems[0].Text;
OpenDirectory(path);
}
When an item is double clicked the method is called and the SelectedItems list should be filled with one Item. I should mention here that it is possible that you have more than one item or none in the selectedItems array so please add some error handling into this method or set the MultiSelect property of the ListView to false.
The text property of the selected Item is filled with the directory name of the Item you clicked because we used the directory name in the ShowDirectoriesInListView method.
Afterwards the OpenDirectory method is called which clears the ListView and adds all the directories of the selected directory to it.
private void OpenDirectory(string path)
{
try
{
lvExplorer.Items.Clear();
string newPath = _controller.AddDirectoryAndGetPath(path);
ShowDirectoriesInListView(newPath);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
In the end you will have a Windows forms class like this
public partial class Form1 : Form
{
private DirectoryController _controller;
public Form1()
{
InitializeComponent();
ImageList imgList = GetFolderImageList();
InitializeListView(imgList);
_controller = new DirectoryController("C:");
OpenDirectory("");
}
private void InitializeListView(ImageList imgList)
{
lvExplorer.DoubleClick += new System.EventHandler(this.lvExplorer_DoubleClick);
lvExplorer.SmallImageList = imgList;
lvExplorer.LargeImageList = imgList;
}
private static ImageList GetFolderImageList()
{
Image folderImg = Image.FromFile(#"C:\Windows\WinSxS\amd64_microsoft-windows-printing-fdprint_31bf3856ad364e35_6.3.9600.17415_none_493b0b9e590044a1\folder.ico");
ImageList imgList = new ImageList();
imgList.Images.Add(folderImg);
return imgList;
}
private void lvExplorer_DoubleClick(object sender, EventArgs e)
{
string path = lvExplorer.SelectedItems[0].Text;
OpenDirectory(path);
}
private void OpenDirectory(string path)
{
try
{
lvExplorer.Items.Clear();
string newPath = _controller.AddDirectoryAndGetPath(path);
ShowDirectoriesInListView(newPath);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void ShowDirectoriesInListView(string path)
{
DirectoryInfo info = new DirectoryInfo(path);
DirectoryInfo parent = info.Parent;
if (parent != null)
{
lvExplorer.Items.Add(new System.Windows.Forms.ListViewItem("...", 0));
}
foreach (DirectoryInfo dInfo in info.GetDirectories())
{
lvExplorer.Items.Add(new System.Windows.Forms.ListViewItem(dInfo.Name, 0));
}
}
}
From here on you should have the knowledge how to use the ListView and add your Files.
SubItems
If you want a detail view you could alter your InitializeListView method
private void InitializeListView(ImageList imgList)
{
lvExplorer.DoubleClick += new System.EventHandler(this.lvExplorer_DoubleClick);
lvExplorer.SmallImageList = imgList;
lvExplorer.LargeImageList = imgList;
CreateHeaders();
lvExplorer.View = View.Details;
}
private void CreateHeaders()
{
ColumnHeader header;
header = new ColumnHeader();
header.Text = "DirectoryName";
lvExplorer.Columns.Add(header);
header = new ColumnHeader();
header.Text = "LastWriteTime";
lvExplorer.Columns.Add(header);
}
We create here two header columns and set the View property of our ListView to Details.
If you now add subItems to your ShowDirectoriesInListView method you have a very nice detail view of your directories.
private void ShowDirectoriesInListView(string path)
{
DirectoryInfo info = new DirectoryInfo(path);
DirectoryInfo parent = info.Parent;
if (parent != null)
{
lvExplorer.Items.Add(new System.Windows.Forms.ListViewItem("...", 0));
}
foreach (DirectoryInfo dInfo in info.GetDirectories())
{
ListViewItem item = new System.Windows.Forms.ListViewItem(dInfo.Name, 0);
item.Tag = dInfo.Name;
//Add some subitems here
System.Windows.Forms.ListViewItem.ListViewSubItem subItem = new ListViewItem.ListViewSubItem();
subItem.Text = dInfo.LastWriteTime.ToShortDateString();
item.SubItems.Add(subItem);
lvExplorer.Items.Add(item);
}
}
These sub-items (listed in the ListViewItem.SubItems collection) are a bit confusing, if you are using a ListView for the first time. The items represent the first column of your ListView, where as the sub-items represent the columns to the right. So the sub-items are not, as you could think, the sub-directories you might have in mind.
This post shows you how you can handle the double click event on a item: https://stackoverflow.com/a/12874269/880990
Look into the DirectoryInfo and FileInfo classes of the System.IO namespace for file operations.
I have an initial XML file stored in my phone which is accessed to select a number of elements. Each element has a corresponding online XML file which I need to access to get more information.
string name, photo;
foreach (int num in combi)
{
no = xElem.Descendants("employee").ElementAt(num).Descendants("no").First().Value;
WebClient wc = new WebClient();
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler
(Info_DownloadStringCompleted);
wc.DownloadStringAsync(new Uri
("http://example.com/" + name));
list.Add(new Person(no, name, photo);
}
void Info_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null) return;
XElement xml = XElement.Parse(e.Result);
name = xml.Element("name").Value;
photo = xml.Element("photo").Value;
}
However, it seems that the list.Add goes first before the XML is downloaded resulting into a list with empty values for name and photo. I confirmed this by placing MessageBox in both the foreach loop and Info_DownloadStringCompleted. Is there a better way of doing this?
To add to #MikkoVitala's answer, better for you to use ObservableCollection for the list.
ObservableCollection has built-in mechanism to notify UI to refresh whenever item added to or removed from collection. So it doesn't matter when you bind list to ListBox, the ListBox will always display up to date member of list :
string name, photo;
ObservableCollection<Person> list = new ObservableCollection<Person>();
foreach (int num in combi)
{
.....
}
MyListBox.ItemsSource = list;
Problem with your solution is that in your iteration over combi you'll try to use fields name and photo which are only assigned to in your eventhandler Info_DownloadStringCompleted.
Since DownloadStringAsync is, well... asynchronous, you'll exit foreach before DownloadStringCompleted event is raised, your eventhandler called and ultimately your fields been assigned to.
You can correct this by moving your list-add-logic to be executed after event has been raised.
....
wc.DownloadStringCompleted += (sender, args) =>
{
// Code from your Info_DownloadStringCompleted event handler
if (args.Error != null)
return;
XElement xml = XElement.Parse(args.Result);
name = xml.Element("name").Value;
photo = xml.Element("photo").Value;
// Now your fields are assigned and you can do-what-ever-with-'em
list.Add(new Person(no, name, photo);
};
....
If you like you can use extension methods to utilize async/await keywords and make it simpler and easier to read and understand. See SO answer here https://stackoverflow.com/a/13174270/1061668
Then above becomes (remember to mark method async)
....
// Note that exception is not handled in this example....
xml = await wc.DownloadStringTask(new Uri("http://example.com/" + name));
name = xml.Element("name").Value;
photo = xml.Element("photo").Value;
list.Add(new Person(no, name, photo);
....
I new to programming and C# and seemed to have worked myself into a bit muddle with the above.
What i am trying to do is create a front end for the living room media pc nothing to fancy to start with as i understand this is a mamoth task for a total noobie like me.
Ive flapped about and am totally fine with launching external exe's,storing/loading resouces ect.. and been very happy with my results for my 2 week surfing.
So im starting off my project by just launching an emulator to start with and what i would like to do is scan a folder for zip files and image files and if it finds matching image and zip files it displays an image in a list view for each zip found.
So i populate my listboxes like this and get my 2 listboxes showing the stuff i want to see.
PopulateListBox(listBox1, "\\SomePath\\", "*.zip");
PopulateListBox(listBox2, "\\Images\\", "*.jpg");
private void PopulateListBox(ListBox lsb, string Folder, string FileType)
{
DirectoryInfo dinfo = new DirectoryInfo(Folder);
FileInfo[] Files = dinfo.GetFiles(FileType);
foreach (FileInfo file in Files)
{
lsb.Items.Add(file.Name);
}
}
So i now have my 2 listboxes and can see i have game1.zip and game1.jpg, great now i can populate my listview with the game1 image and launch the emulator he say's simple.
This is how i am currently populating the listview.
PopulateListView();
private void PopulateListView()
{
if (listBox1.Items.Contains("game1.zip"))
{
if (File.Exists("\\Images\\game1.jpg"))
{
imageList1.Images.Add(Image.FromFile("\\Images\\game1.jpg"));
listView1.Items.Add("", 0);
}
}
if (listBox1.Items.Contains("game2.zip"))
{
if (File.Exists("\\Images\\game2.jpg"))
{
imageList1.Images.Add(Image.FromFile("\\Images\\game2.jpg"));
listView1.Items.Add("", 1);
}
}
}
This is how i am currently launching and it works ok.
// launch item
private void listView1_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (listView1.Items[0].Selected == true)
{
string rom = "\\" + listBox1.Items[0].ToString();
// Launch code in here
}
if (listView1.Items[1].Selected == true)
{
string rom = "\\" + listBox1.Items[1].ToString();
// Launch code in here
}
}
So what the problem you may ask ?
Instead of keep typing in all that info for each item i want to use some kind of statment if possible and i dont know what to search for which does not help.
The image name will allways match the zip name just need to loose the file extensions so to populate the listview somthing like this.
if (listbox1 item = listbox2 item)
{
add to imagelist and listview automaticly with same index
}
Then i want to be able to launch just using somthing like this.
private void listView1_MouseDoubleClick(object sender, MouseEventArgs e)
{
string rom = "\\" + listview.selected.item.tostring;
// Launch code in here
}
Hope im making sense im at my witts end.
Regards
Derek
If i understand correctly, you want to read, match and show programmatically all images and zips that are the same and when the user clicks a list view row to start the rom. This can be done as follows:
read all images - there is no need to check if the file exists, because if it does not, then GetFiles will not find it!
read all roms - same as above!
take all names that have both image and zip file (that are the same)
fill image list and list view at the same time
bind list view item with the correct image
store rom location
handle mouse click on row and fetch rom location
do something with rom file
The code:
public ListForm()
{
InitializeComponent();
// path to images and roms
const string location = #"d:\temp\roms\";
// bind image list with list view
listViewControl.SmallImageList = imageList;
// get all images without extension
var images = System.IO.Directory.GetFiles(location, "*.gif").Select(f => System.IO.Path.GetFileNameWithoutExtension(f)).ToList();
// get all roms without extension
var zips = System.IO.Directory.GetFiles(location, "*.zip").Select(f => System.IO.Path.GetFileNameWithoutExtension(f)).ToList();
// find all entries (images and zips) that have the same name
var matching = images.Intersect(zips);
var imageIndex = 0;
// fill image list and list view at the same time and store rom location
foreach (var match in matching)
{
// path to file without extension
var file = System.IO.Path.Combine(location, match);
// add image to image list
imageList.Images.Add(match, Bitmap.FromFile(string.Format("{0}.gif", file)));
// create list view item
var lvi = new ListViewItem(match);
// and set list view item image
lvi.ImageIndex = imageIndex;
// store rom location
lvi.Tag = string.Format("{0}.zip", file);
imageIndex++;
// and show
listViewControl.Items.Add(lvi);
}
}
private void listViewControl_MouseClick(object sender, MouseEventArgs e)
{
// when user clicks an item, fetch the rom location and go
var item = listViewControl.GetItemAt(e.X, e.Y);
if (item != null)
{
var pathToRom = item.Tag as string;
// do something with rom ...
}
}
There is no need to handle the image list and the list view separately. The output looks like:
you can Populate the images using the names from the listView:
private void PopulateListView()
{
foreach (string curr in listBox1.Items)
{
string changed = "\\Images\\" + curr.Replace(".zip", ".jpg");
if (File.Exists(changed))
{
imageList1.Images.Add(Image.FromFile(changed));
listView1.Items.Add("", 1);
}
}
}
and use the selected item in the MouseDoubleClick event
// launch item
private void listView1_MouseDoubleClick(object sender, MouseEventArgs e)
{
string selected = (string) listBox1.SelectedItem;
string rom = "\\" + selected;
// Launch code in here
}
If I'm understanding your problem correctly, you can switch out your tower of if statements with this:
var item = listView1.SelectedItem
string rom = "\\" + item.ToString()
// Launch code in here
And to populate it, you can change this:
if (listBox1.Items.Contains("game2.zip"))
{
if (File.Exists("\\Images\\game2.jpg"))
{
imageList1.Images.Add(Image.FromFile("\\Images\\game2.jpg"));
listView1.Items.Add("", 1);
}
}
To this:
List<string> files = new List<string> { "game1.zip", "game2.zip" };
var index = 0;
foreach (var file in files)
{
if (File.Exists("\\Images\\" + file))
{
imageList1.Images.Add(Image.FromFile("\\Images\\" + file.Replace(".zip", ".jpg")));
listView1.Items.Add("", index);
index++;
}
}
I have a TreeView in my a C# winform. I would like to be able to add a search functionality through a search box.
Basically as the user types in letters (I'm guessing on the _TextChanged event), I show only the nodes that contain childnodes with the inputed letters...
My TreeView contains 53 parent nodes for a total of over 15000 Nodes so I need something a bit performant. I build my TreeView from a csv that I load into a DataTable and then make queries on to get the Parent nodes with associated child nodes...
UPDATE
I have an idea.
The final aim is that when a user doubleclicks on a child node it gets added to a listView.
I had first implemented this search function in a simple list view where I didn't separate my data into categories.
My idea is that once the user starts typing in things, I turn off my Tree view and show the list view instead...
I'll try and implement and see what it gives performance wise... Any critics on this idea are welcome.
Finally this is what I did, it suits my requirements.
I first make a copy of my TreeView and store into fieldsTreeCache. I then clear the fieldsTree. I then search through the cache and add any node containing my search parameter to the fieldsTree. Note here that once you search, you no longer have the parent nodes that show. You just get all of the end nodes. I did this because if not I had 2 choices:
Expand all the parent nodes containing childs that match but then it was slow and one parent might have 50 children which isn't great visually.
Not expand the parent nodes but then you just get the categories and not the children nodes that you're searching for.
void fieldFilterTxtBx_TextChanged(object sender, EventArgs e)
{
//blocks repainting tree till all objects loaded
this.fieldsTree.BeginUpdate();
this.fieldsTree.Nodes.Clear();
if (this.fieldFilterTxtBx.Text != string.Empty)
{
foreach (TreeNode _parentNode in _fieldsTreeCache.Nodes)
{
foreach (TreeNode _childNode in _parentNode.Nodes)
{
if (_childNode.Text.StartsWith(this.fieldFilterTxtBx.Text))
{
this.fieldsTree.Nodes.Add((TreeNode)_childNode.Clone());
}
}
}
}
else
{
foreach (TreeNode _node in this._fieldsTreeCache.Nodes)
{
fieldsTree.Nodes.Add((TreeNode)_node.Clone());
}
}
//enables redrawing tree after all objects have been added
this.fieldsTree.EndUpdate();
}
Here's a small simple example (with code from msdn) is that a very simple way to filter out the TreeView node displays.
winforms in a tree view you can only add or remove TreeNode.
The search for the nodes can still be improved if the nodes are stored with their data into a dictionary (with a unique key).
using System.Collections;
using System.Windows.Forms;
namespace FilterWinFormsTreeview
{
// The basic Customer class.
public class Customer : System.Object
{
private string custName = "";
protected ArrayList custOrders = new ArrayList();
public Customer(string customername) {
this.custName = customername;
}
public string CustomerName {
get { return this.custName; }
set { this.custName = value; }
}
public ArrayList CustomerOrders {
get { return this.custOrders; }
}
}
// End Customer class
// The basic customer Order class.
public class Order : System.Object
{
private string ordID = "";
public Order(string orderid) {
this.ordID = orderid;
}
public string OrderID {
get { return this.ordID; }
set { this.ordID = value; }
}
}
// End Order class
public static class TreeViewHelper
{
// Create a new ArrayList to hold the Customer objects.
private static ArrayList customerArray = new ArrayList();
public static void FilterTreeView(TreeView treeView1, string orderText) {
if (string.IsNullOrEmpty(orderText)) {
FillMyTreeView(treeView1);
} else {
// Display a wait cursor while the TreeNodes are being created.
Cursor.Current = Cursors.WaitCursor;
// Suppress repainting the TreeView until all the objects have been created.
treeView1.BeginUpdate();
foreach (TreeNode customerNode in treeView1.Nodes) {
var customer = customerNode.Tag as Customer;
if (customer != null) {
customerNode.Nodes.Clear();
// Add a child treenode for each Order object in the current Customer object.
foreach (Order order in customer.CustomerOrders) {
if (order.OrderID.Contains(orderText)) {
var orderNode = new TreeNode(customer.CustomerName + "." + order.OrderID);
customerNode.Nodes.Add(orderNode);
}
}
}
}
// Reset the cursor to the default for all controls.
Cursor.Current = Cursors.Default;
// Begin repainting the TreeView.
treeView1.EndUpdate();
}
}
public static void FillMyTreeView(TreeView treeView1) {
// Add customers to the ArrayList of Customer objects.
if (customerArray.Count <= 0) {
for (int x = 0; x < 1000; x++) {
customerArray.Add(new Customer("Customer" + x.ToString()));
}
// Add orders to each Customer object in the ArrayList.
foreach (Customer customer1 in customerArray) {
for (int y = 0; y < 15; y++) {
customer1.CustomerOrders.Add(new Order("Order" + y.ToString()));
}
}
}
// Display a wait cursor while the TreeNodes are being created.
Cursor.Current = Cursors.WaitCursor;
// Suppress repainting the TreeView until all the objects have been created.
treeView1.BeginUpdate();
// Clear the TreeView each time the method is called.
treeView1.Nodes.Clear();
// Add a root TreeNode for each Customer object in the ArrayList.
foreach (Customer customer2 in customerArray) {
var customerNode = new TreeNode(customer2.CustomerName);
customerNode.Tag = customer2;
treeView1.Nodes.Add(customerNode);
// Add a child treenode for each Order object in the current Customer object.
foreach (Order order1 in customer2.CustomerOrders) {
var orderNode = new TreeNode(customer2.CustomerName + "." + order1.OrderID);
customerNode.Nodes.Add(orderNode);
}
}
// Reset the cursor to the default for all controls.
Cursor.Current = Cursors.Default;
// Begin repainting the TreeView.
treeView1.EndUpdate();
}
}
}
Every node in TreeView has Expanded and IsVisible properties. The number of items which are visible at the same time is limited (TreeView.VisibleCount). Based on this information you can reduce the number of nodes to probe dramatically.
When scanning the node and it's child nodes you can abort recursion as you find the first match inside collapsed node, thus you already know it has at least one child and will be visible anyway.
Perform filtering asynchronously. (use new Task() for instance) Start the first task after minimal number of chars was typed (let's say 3). Every next typed char must cancel the running task and start the new one.