My dynamically created ListViewItems are not displaying in my ListView - c#

I am dynamically creating ListViewItem descendants:
class Application : ListViewItem {
. . .
class LegacyApplication : Application {
I store these first in a List of obects:
private List<object> legacyApps = new List<object>();
..this way:
if (ldbc.appIsLegacy(sPathOfExe)) {
legacyApp = new LegacyApplication(sApp, sTitle, sDesc, sIconFileName, sPathOfExe, appCategoriesForCurrentApp);
}
legacyApps.Add(legacyApp);
...and then I add them to the ListView on the main form this way:
foreach (LegacyApplication LegApp in legacyApps) {
this.listApplications.Items.Add(LegApp);
}
...but the ListView does not display them. It displays the ListView Groups I've created (and each ListViewItem is assigned to one of those groups), but not the ListViewItems themselves...
Updated with requested info:
The constructor for the ListViewItem descendant looks like so:
public LegacyApplication(String AAppName, String ATitle, String ADesc, String AIconFileName, String APathOfExe, List<String> ACategories) {
base.Name = String.Format("legapplvi{0}", AAppName);
base.Text = ATitle; // "Title" is a short description - between exe name and Description
base.ToolTipText = ADesc;
base.EnsureVisible();
// "base" above means ListViewItem; "base" below refers to our Application class*
base.Categories = ACategories;
base.ExePath = APathOfExe;
base.IconFileName = AIconFileName;
}
which adds the following properties to ListViewItem:
public string ExePath
public string IconFileName
public string Category
public List Categories
LegacyApplication adds no further properties to (our) Application class.
I'm not sure what the respondent below means by "subitems" - the ListViewItems are subitems of the ListView Groups, perhaps...?
Updated with unrequested info (TMI?):
OK, I'm thinking I can add columns this way, once all the Groups are assigned to the ListView:
for(var item in listApplications.Groups) {
listApplications.Columns.Add(item)
}
...but now, how do I add specific ListViewItems to particular columns?
Updated after getting it to (sort of) work:
Commenting out this:
listApplications.View = View.Details;
...gets the Items to show. HOWEVER, the Text is truncated, which caused me to pose another question here:
I need to display the entire Text of my ListViewItems (not truncated)

Did you try to create a ListViewItem first and then add into the List instead of using
this.listApplications.Items.Add(LegApp);
Can you provide more details with a code snippet

Related

Name dynamically generated combobox items

I am currently working on a WPF project in .NET Core 3.1. During the execution I have to read some file names to a combobox. I do that by first adding them to a list. After that I iterate through that list and add them to the combobox.
Problem
Because they should only be accessible if an Item from another combobox is selected, I need to disable the items I just added. Because of that, and to later enable them, I need to name them. But I could not figure our how to do that.
Here is my code:
string[] allAircraft;
allAircraft = Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + #"\ApplicationName\Aircraft\", "*.lxml", SearchOption.TopDirectoryOnly);
foreach (var currentAircraft in allAircraft)
{
aircraft.Add(System.IO.Path.GetFileNameWithoutExtension(currentAircraft));
}
foreach (string currentListItem in aircraft)
{
comboAircraft.Items.Add(currentListItem);
}
foreach (ComboBoxItem currentComboItems in comboAircraft.Items)
{
currentComboItems.Name = "comboAircraftItems" + currentComboItems.Content;
}
At the line foreach (ComboBoxItem currentItems in comboAircraft.Items) I get the Exception:
System.InvalidCastException: 'Unable to cast object of type 'System.String' to type 'System.Windows.Controls.ComboBoxItem'.'
I am really running out of ideas. I googled one entire day but could not find anything.
Have a nice day everyone!
# t-kldw, Your task is very difficult to solve when working directly with UI elements.
But it is very simple to solve, if you separate the Data and their Presentation, as it should be in MVVM.
Unfortunately, what you wrote contains few details for an example with full implementation.
I can give only partial advice.
It is necessary to create an additional container-class for the data needed in one combo box.
This class MUST have an INotifyPropertyChanged implementation.
To simplify, take implementation from the topic OnPropertyChanged and instance property
/// <summary>Base class implementing INotifyPropertyChanged.</summary>
public abstract class BaseINPC : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Called AFTER the property value changes.</summary>
/// <param name="propertyName">The name of the property.
/// In the property setter, the parameter is not specified. </param>
public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
An example class with two properties: File Name and IsEnabled to allow selection.
public class FileItem : BaseINPC
{
private string _fileName;
private bool _isEnabled;
public string FileName { get => _fileName; set { _fileName = value; RaisePropertyChanged(); } }
public bool IsEnabled { get => _isEnabled; set { _isEnabled = value; RaisePropertyChanged(); } }
}
So that I can give further advice, you need to provide more details of your task.
For example, I don’t even have a clue why currentComboItems.Name might be needed ....
I think that you are clearly doing something wrong.
You are trying to convert String to ComboBoxItem. When you populate your combobox with data, you add String, not ComboBoxItem. So you should use String in the last foreach loop.
foreach (string currentComboItems in comboAircraft.Items)
{
currentComboItems.Name = "comboAircraftItems" + currentComboItems;
}
Another solution is to add ComboBoxItem to your combobox instead of String.
foreach (string currentListItem in aircraft)
{
ComboBoxItem item = new ComboBoxItem()
{
Content = currentListItem,
Name = "comboAircraftItems" + currentListItem
};
comboAircraft.Items.Add(item);
}
ItemsControl.Items only holds data objects. In order to render this items, each is wrapped into a container e.g., ComboBoxItem. These UI containers are generated by a ItemContainerGenerator, which is associated with the current ItemsControl. The ItemContainerGenerator is also responsible to track the mapping between the data item and its UI container.
To obtain the container of an item you use the ItemsControl.ItemContainerGenerator:
DependencyObject itemContainer = ItemContainerGenerator.ContainerFromItem(item);
Your simplified code becomes:
List<string> allAircrafts = Directory.EnumerateFiles(
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
#"\ApplicationName\Aircraft\"),
"*.lxml",
SearchOption.TopDirectoryOnly)
.Select(Path.GetFileNameWithoutExtension)
.ToList();
comboAircraft.ItemsSource = allAircrafts;
foreach (string item in allAircrafts)
{
var itemContainer = comboAircraft.ItemContainerGenerator.ContainerFromItem(item) as ComboBoxItem;
itemContainer.Name = "comboAircraftItems" + itemContainer.Content;
}
But note, that the approach you chose has issues. When UI virtualization is enabled for ItemsCopntrol, then only the visible items have their containers generated. Items out side the viewport of the scroll viewer are not generated and can't be returned by the ItemContainerGenerator.
Since UI virtualization is not enabled for the ComboBox by default, this still can work, as long you don't enable this performance optimization feature.
The optimal solution would need you to create a filtered source collection for the ComboBox that displays the filtered items. This way you won't clutter the control with items that are not of interest for the user and are not selectable anyway.
Create a collection of type ObservableCollection<string> which contains the selected items of the primary ComboBox you named comboAircraft.
Set this collection as ItemsSource of the second filtered ComboBox.
Alternatively create a CollectionViewSource of the original source collection and bind it to the secondary ComboBox. ICollectionView supports filtering.
The proper solution
Using either of the two recommended solutions eliminates the requirement to care about item containers. This adds flexibility and simplifies your code.

Extend ListViewItem in WinForms

I've made a fatal mistake.
I totally forgot to extend the ListView to include some important features. And now it's way too late to just delete the ListView, add those features and re-add the ListView all over again.
What I need to add is simple, and I've done it before, but I don't think I can do it the same way as before because when I did it before I did it before I created the ListView and placed it on the Form.
I need to be able to do:
ListViewItem item = new ListViewItem();
item.SomeNewProperty = "yay";
But how can I extend the functionality now that I've already created a ListView control and added it to the form and set all of its properties and setup all of the events. If I delete the ListView and extend it then re-add it everything will be lost and I'd have to start over again.
Is there a way to extend it to add that new property to ListViewItem without starting from scratch?
Each control in System.Windows.Form contain a property called Tag, that is an Object DataType, if you just want to save one value in each ListViewItem you should do the following:
ListViewItem Item = new ListViewItem();
Item.Tag = "Something";
But if you want to save multiple values in each ListViewItem you can create a class that contain all the info thet you want to set. Ex.
public class MyClass
{
public string Value1 {get; set;}
public string Value2 {get; set;}
}
MyClass oInfo = new MyClass();
oInfo.Value1 = "Smething";
oInfo.Value2 = "Is Good";
ListViewItem Item = new ListViewItem();
Item.Tag = oInfo;
To retrieve the object do the following:
MyClass oRetrieveInfo = (MyClass)LisView.Items[index].Tag;
MessageBox.Show(oRetrieveInfo.Value1 + " " + RetrieveInfo.Value2);

WPB ListBox bind to control inside a class

I have a WPF ListBox of Grids that I create as follows:
I define the listbox in XAML with a simple declaration as follows:
<ListBox Name="MyListbox" >
</ListBox>
In code, I dynamically create an arbitrary number of Grid items (System.Windows.Controls.Grid), and I add them to the ListBox. Something like:
foreach (MyDataType myItem in MyDataList)
{
Grid newGrid = new Grid();
// Code that sets the properties and values of the grid, based on myItem
MyListbox.Items.Add(newGrid);
}
This works great, and everything looks the way that I want it to.
Now, I've decided that in my ListBox, I also want to store a reference to the actual myItem object, so that I can reference it later.
My idea was to create a new class like:
public class ListGridItemNode
{
public MyDataType theItem;
public Grid theGrid;
public ListGridItemNode(MyDataType inItem, Grid inGrid)
{
theItem = inItem;
theGrid = inGrid;
}
}
And then change my code to:
foreach (MyDataType myItem in MyDataList)
{
Grid newGrid = new Grid();
// Code that sets the properties and values of the grid, based on myItem
MyListbox.Items.Add(new ListGridItemNode(myItem,newGrid));
}
Of course, now my listbox instead of displaying the grids, just displays the text "MyApp.ListGridItemNode".
My question is: How do I tell the ListBox to go a level deeper and display the actual Grids inside of each ListGridItemNode object?
I suspect that this has something to do with bindings, but I can't find any examples that work the way that I'm doing it. Most of what I'm finding only shows binding to a string within an object, not an entire control.
Couldn't you just use the Tag property of the Grid object?
newGrid.Tag = myItem;
Then later:
Grid grid; // obtain Grid object somehow
MyItem myItem = (MyItem) grid.Tag;

How to access the Items of a User Control

I have a C# Form that prints multiple instances of a User Control. Let's say that the form prints 5 instances of the User Control (Please see the link attached). How can I store/save the data inputted in all User Controls? Thanks
Here is the screenshot of the C# Form:
You'll have to store the User Controls when you instantiate them in a List or something.
You could have a class like this:
class SomeUC : UserControl
{
public SomeUC()
{
}
// A public method.
public string GetData()
{
return textBox1.Text;
}
}
Where textBox1 is the Name of a TextBox in your SomeUC
And then inside your main or something.
// Instantiate a List that will hold your UserControls, this has to be outside all methods
List<SomeUC> list = new List<SomeUC>();
// And now when you want to build your UCs
// Instantiate your UserControl
SomeUC uc1 = new SomeUC();
// Store your UserControl in a List or something (Can't help you with that)
list.Add(uc1);
Add as much as you want.
A List is not the only way you can do that, but since you don't know how many UserControls you're going to build beforehand, it makes since to use a List.
And then you can access them from the list by their index.
SomeUC uc1 = list[0];
string data = uc1.GetData();
This is an example of accessing one control (the TextBox) in your SomeUC. For other classes (such as the ComboBox) the interaction is different. Meaning you won't have a Text property in the ComboBox. You'll have to figure out things like that on youself. A little research is what it takes. You can always come back if you couldn't find a solution for something.
You can create a property like this for each item in user control.
public string DG
{
get
{
return txtDG.Text;
}
set
{
txtDG.Text = value;
}
}
Then you can access the control value by using following line in your form.
supposed you have created a usercontrol MyControl and you have placed some object of this control in FlowLayoutPenal (pnlFLP).
To get value from control
string DG = ((MyControl)pnlFLP.Controls[0]).DG;
To set value in control
((MyControl)pnlFLP.Controls[0]).DG = "1";
Try this code for accessing user control in the page
Dim txtName As TextBox = TryCast(UserControlName.FindControl("txtName"), TextBox)

Binding collections to DataGridView in Windows Forms

I'm trying to bind a collection to a DataGridView. As it turns out it's impossible for the user to edit anything in this DataGridView although EditMode is set to EditOnKeystrokeOrF2.
Here is the simplified code:
public Supplies()
{
InitializeComponent();
List<string> l = new <string>();
l.Add("hello");
this.SuppliesDataGridView.DataSource = l;
}
It also doesn't work when I change the collection type to SortableBindingList, Dictionary or even use a BindingSource.
What can be wrong here?
For me the following method works as expected:
Open your form (usercontrol, etc.) with the designer
Add a BindingSource to your form
Select the BindingSource in your form and open the properties page
Select the DataSource property and click on the down arrow
Click on Add project data source
Select Object
Select the object type you wish to handle
This should be the type that will be handled by your collection, not the CustomCollection itself!
Show the available data sources by selecting from the MenuBar Data - Show Data Sources
Drag and Drop your ItemType from the DatasSources on your form
Go into the code of your form and bind your CustomCollection to the BindingSource
var cc = new CustomCollection();
bindingSource1.DataSource = cc;
Remarks:
The DataGridView is just the last part in your chain to (dis)allow changing, adding and removing objects from your list (or CustomCollection). There is also a property AllowNew within the BindingSource and the ICollection interface has a property IsReadOnly which must be set to false to allow editing. Last but not least, the properties of your class within the collection must have a public setter method to allow changing of a value.
Try this:
public class CustomCollection { public string Value { get; set; } }
public Supplies()
{
InitializeComponent();
List<CustomCollection> l = new List<CustomCollection> { new CustomCollection { Value = "hello" } };
this.SuppliesDataGridView.DataSource = l;
}
Once you've set the DataSource property you'll then want to fire off the DataBind() method.
this.SuppliesDataGridView.DataSource = l;
this.SuppliesDataGridView.DataBind();
UPDATE:
As you rightly pointed out in the comments, the DataBind() method doesn't exist for this control.
This link might provide some helpful information: http://msdn.microsoft.com/en-us/library/fbk67b6z%28v=VS.90%29.aspx

Categories

Resources