WPF Add listview item in listview created dynamically - c#

This is my first question here :)
I create a listview columns form a textfile in Window_Loaded()
After this I want to add items to this listview...
foreach (KeyValuePair<string, string> key in App.sysIni.monitorProperties)
{
GridViewColumn column = new GridViewColumn();
column.Header = key.Value;
column.Width = 70;
GridViewControlMonitor.Columns.Add(column);
}
After this I create listview items:
string[] rowItems = new string[list.Count];
for (int i = 1; i < list.Count; i++)
{
rowItems[i] = list[i];
}
var item = new ListViewItem { Content = rowItems};
itemsList.Add(item);
And add this items in listview:
RequestMonitorListV.Items.Add(item);
And my listview is populated with "String[] Array" not values...
Is possible to create a listview item with content and some kind of binding for that content?
var item = new ListViewItem { Content = rowItems, !!soomeBindingOptinos!!};
Can someone help me with this issue?
Thank you in advance

You should bind the DisplayMemberBinding property of each GridViewColumn to a property of an object in the ListView's ItemsSource collection:
for (int i = 0; i<App.sysIni.monitorProperties.Count; ++i)
{
KeyValuePair<string, string> key = App.sysIni.monitorProperties[i];
GridViewColumn column = new GridViewColumn();
column.Header = key.Value;
column.Width = 70;
column.DisplayMemberBinding = new Binding("[" + i + "]");
GridViewControlMonitor.Columns.Add(column);
}
...and set the latter to an IEnumerable like for example a string[]:
string[] rowItems = new string[list.Count];
for (int i = 0; i<list.Count; i++)
{
rowItems[i] = list[i];
}
RequestMonitorListV.ItemsSource = rowItems;

I'm going to be focusing on the second section of your code, as that seems to be the portion that is actually causing trouble. Unfortunately there are a few details missing, so I'll be making some assumptions on intent, type, etc.
Here is the .xaml I'll be working against:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView x:Name="myListView" HorizontalAlignment="Left" Height="301" Margin="10,10,0,0" VerticalAlignment="Top" Width="498">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
And here is my interpretation of that chunk of C#:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var myListViewVar = this.FindName("myListView") as ListView;
string[] rowItems = new string[10];
for (int i = 0; i < 10; i++)
{
rowItems[i] = i.ToString();
}
var item = new ListViewItem { Content = rowItems };//You are adding your string array to a singular element. It does not want it. It wants a nice string.
var itemsList = new List<ListViewItem>();//You then wrap this singular item in a list. I'm inferring the type here, since the rest of your code does not reference it.
itemsList.Add(item);
myListViewVar.Items.Add(item);//You then add the ARRAY to the list of items. Again, this is not desired. See https://msdn.microsoft.com/en-us/library/system.windows.forms.listview.items(v=vs.110).aspx for usage examples.
}
}
}
As explained above, WPF doesn't know how to stringify your array. Interpreting from what the rest of your code implies, what I think you want is:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var myListViewVar = this.FindName("myListView") as ListView;
string[] rowItems = new string[10];
for (int i = 0; i < 10; i++)
{
rowItems[i] = i.ToString();
}
for (int i = 0; i < 10; i++)
{
var item = new ListViewItem { Content = rowItems[i] };//Each ListViewItem holds an individual string rather than an array
myListViewVar.Items.Add(item);//Then add that ListViewItem.
}
}
}
}
Obviously this has a redundancy in the form of the second loop, but I wanted to keep with your code where you store everything outside of the loop.
As for data binding, I'd recommend looking at WPF Binding to local variable
I don't use WPF too often, but it looks like what you want.
Hope this helps. Let me know if I've missed the mark on what you're intending.

Related

At Runtime Add X number of ComboBoxes with SelectedItem to DataGrid (WPF)

I would like to create an entire row of ComboBoxes in a DataGrid. I have made some progress with the following:
// Declare it
private DataGridComboBoxColumn CreateCustomComboBoxDataSouce(string ColumnName)
{
string[] data = { "Date", "LEInt", "String" };
DataGridComboBoxColumn dgCmbCol = new DataGridComboBoxColumn();
dgCmbCol.Header = ColumnName;
dgCmbCol.ItemsSource = data;
return dgCmbCol;
}
// Later somewhere you can add this to create 20 columns:
for (int i = 0; i < 20; i++)
{
DataGridComboBoxColumn newColumn = CreateCustomComboBoxDataSouce("Column-" +i);
}
// Sadly nothing is shown unless you manually specify a new row and an
// extra column as done here below, but then you get an extra column.
DataTable table = new DataTable();
table.Columns.Add("|", typeof(string));
table.Rows.Add("");
DataGridCombo.DataContext = table;
The XAML is kept to a minimum:
<DataGrid x:Name="DataGridCombo" ItemsSource="{Binding}" Margin="0,0,0,0" />
Is there a way to set the default SelectedValue of each ComboBox? Within my for loop I do have access to the desired setting. Also, is there a way to git it to show without adding the extra column? This DataGrid is being lined up with another DataGrid that won't have that extra column.
Is there a way to set the default SelectedValue of each ComboBox?
Add a DataColumn to the DataTable per DataGridComboBoxColumn, and then set the value of the column for each row to the desired selected value:
const int n = 20;
DataTable table = new DataTable();
for (int i = 0; i<n; i++)
{
string columnName = "Column-" + i;
DataGridCombo.Columns.Add(CreateCustomComboBoxDataSouce(columnName));
table.Columns.Add(columnName, typeof(string));
}
table.Rows.Add(new object[n]);
//select some values...
table.Rows[0][1] = "LEInt";
table.Rows[0][5] = "String";
DataGridCombo.DataContext = table;
Also, is there a way to git it to show without adding the extra column?
Set the HorizontalAlignment property to Left to prevent the DataGrid from stretching horizontally:
<DataGrid x:Name="DataGridCombo" ItemsSource="{Binding}" HorizontalAlignment="Left" />
After a lot of working around the issue, I eventually came to the conclusion that what I was trying to do was either not possible, or unreasonable. I changed to using a StackPanel with horizontal orientation. Here are the relevant snippets:
//XAML Layout
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Name="ComboBoxPanel" Grid.Row="1" Margin="0,0,0,0" HorizontalAlignment="Left" />
//Function to create new box and set options
private ComboBox CreateComboBox(string ColumnName, string selectedItem, int width)
{
string[] data = { "Date", "LEInt", "String" };
ComboBox dgCmbCol = new ComboBox();
dgCmbCol.ItemsSource = data;
dgCmbCol.Name = ColumnName;
dgCmbCol.Width = width;
dgCmbCol.SelectedItem = selectedItem;
dgCmbCol.SelectionChanged += DataTypeSelectionChanged;
return dgCmbCol;
}
// This reads a JSON file and uses it's options to set the width of all the columns
// for the DataGrid and matches the ComboBoxes to it so they all line up
dynamic TableData = JsonConvert.DeserializeObject(Config.DataLayoutDefinition);
ComboBoxPanel.Children.Clear(); //Since this is run on user interaction, clear old contents.
for (int i = 0; i < DataGridView.Columns.Count; i++)
{
int width = TableData.data[i].ColumnWidth;
if (TableData.data[i].DataType == "String")
{
width = 125;
}
else if (TableData.data[i].DataType == "Date")
{
width = 150;
}
else if (TableData.data[i].DataType == "LEInt")
{
width = 80;
}
else
{
width = 100;
}
ComboBoxPanel.Children.Add(CreateComboBox(TableData.data[i].Column.ToString(), TableData.data[i].DataType.ToString(), width));
DataGridView.Columns[i].Width = width;
}

C# WPF dynamic listView without SubItems

I have ListView that is dynamic and Columns name need to change dynamic so i create this:
var gridView = new GridView();
this.lvWorkers.View = gridView;
foreach(string c in columnName)
{
gridView.Columns.Add(new GridViewColumn
{
Header = c.Remove(0, 5),
DisplayMemberBinding = new Binding(c)
});
}
Now i want add Items to this list but there is no SubItems like in Win Forms. I find similar problems, the answer was to make class that is defined. Add Items to Columns in a WPF ListView
I need to add Items dynamic.
Win Forms which is not work.
foreach (OneStudentEvent e in oneEventList)
{
ListViewItem item = new ListViewItem(e.Indeks.ToString());
item.SubItems.Add(e.eventString);
...
lvWorkers.Items.Add(item);
}
EDIT
Now i remove DisplayMemberBinding = new Binding(c)
and add:
lvWorkers.ItemsSource = getList();
private ArrayList getList()
{
ArrayList data = new ArrayList();
for(int i=0; i<20; i++)
{
List<string> tempList = new List<string>();
for(int j = 0; j < 30; j++)
{
tempList.Add(j.ToString());
}
data.Add(tempList);
}
return data;
}
And i don't see string in list but word: (Collection). I know that this is my bad to show collection no single string, but i don't know hot to make it.

Add checkboxes to a list of dynamically generated result

I have a list of result listed after extracting from a .txt file. I would like to add a checkbox behind every results listed. The following is my code:
private void LoadFile() {
List<string> lines = new List<string>();
try
{
StreamReader sr = new StreamReader("test.txt");
while (!sr.EndOfStream)
{
lines.Add(sr.ReadLine());
}
sr.Close();
for (int i = 3; i < lines.Count; i++)
{
resultsTreeView.Items.Add(lines[i].ToString().Substring(67,17));
resultsTreeView.Items.Add(CheckBox[i]);
}
How can I add checkboxes as the results extracted will change every time? I would like to track which boxes has checked as well so that I can print the result to users. Thank you!
for (int i = 3; i < lines.Count; i++)
{
resultsTreeView.Items.Add(lines[i].ToString().Substring(67,17));
resultsTreeView.Items.Add(new CheckBox());
// resultsTreeView.Items.Add(BuildCheckBox())
}
OR
CheckBox BuildCheckbox()
{
CheckBox C = new CheckBox();
return C;
}
That's all you need to create a checkbox, or you can create a function that returns a checkbox, inside it you create a new instance of checkbox and set the attributes/subscribe to events the way you want and return it.
As for the tracking which checkboxes are checked, I only need you to provide me with the type of your "resultsTreeView"
EDIT :
To loop through checkboxes in the TreeView and do something on the checked ones:
resultsTreeView.Items.OfType<CheckBox>().ToList()
.ForEach(C =>
{
if (C.IsChecked.HasValue && C.IsChecked.Value == true)
{
//DoSomething
}
});
I am not sure exactly what you are looking for. I assume resultsTreeView is a TreeViewItem. and I also assume that you are working in wpf. You can do the following through wpf.
for (int i = 0; i < lines.Count(); i++)
{
resultsTreeView.Items.Add(lines[i].ToString().Substring(67,17));
}
<TreeView x:Name="resultsTreeView" HorizontalAlignment="Left" Height="100" Margin="37,344,0,0" VerticalAlignment="Top" Width="257" >
<TreeView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
<CheckBox/>
</StackPanel>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Something similar could be done through code behind
for (int i = 0; i < mylist.Count(); i++)
{
resultsTreeView.Items.Add(mylist[i]);
}
resultsTreeView.ItemTemplate = TreeViewDataTemp;
And then create TreeViewDataTemp the following way
private static DataTemplate TreeViewDataTemp
{
get
{
DataTemplate TVDT = new DataTemplate();
FrameworkElementFactory Stack = new FrameworkElementFactory(typeof(StackPanel));
Stack.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
FrameworkElementFactory TextB = new FrameworkElementFactory(typeof(TextBlock));
TextB.SetValue(TextBlock.TextProperty, new Binding());
FrameworkElementFactory ChkBox = new FrameworkElementFactory(typeof(CheckBox));
Stack.AppendChild(TextB);
Stack.AppendChild(ChkBox);
TVDT.VisualTree = Stack;
return TVDT;
}
}
The above gives you 1 item which is text together with a checkbox.
Alternatively your method will add a checkbox as a new item after every string item that you add.. which is
for (int I=0; I<lines.Count(); I++)
{
resultsTreeView.Items.Add(mylist[i]);
resultsTreeView.Items.Add(new CheckBox());
}

Add items into DataContext via loop

I have following code in C#, WPF:
base.DataContext = new DataTemplate[]
{
new DataTemplate
{
lblText = "First",
txtBoxContent = ""
},
new DataTemplate
{
lblText = "Second",
txtBoxContent = "Something"
}
};
but i need to fill DataContext dynamically from database. My idea looks like this:
base.DataContext = new DataTemplate[]
{
for(int i = 0; i< dsTmp.Tables[0].Rows.Count; i++)
{
new DataTemplate
{
lblText = "Count: ",
txtBoxContent = dsTmp.Tables[0].Rows[i][0].ToString();
}
}
};
When i type this, it yells some syntax errors on me;
Could anybody tell me, how to write it correctly?
You can't have code inside object initializer syntax. Why not simply do this:
var list = new DataTemplate[dsTmp.Tables[0].Rows.Count];
for(int i = 0; i< dsTmp.Tables[0].Rows.Count; i++)
{
var item = new DataTemplate
{
lblText = "Count: ",
txtBoxContent = dsTmp.Tables[0].Rows[i][0].ToString();
};
list[i] = item;
}
this.DataContext = list;
MBen and Habib have already answered why the for is failing, because you can't do a loop in an object initializer and have provided loop alternatives.
Alternatively you can use linq to perform an initialization.
this.DataContext=dsTmp.Tables[0].Rows.Select(
x=>new DataTemplate {
lblText = "Count: ",
txtBoxContent=x[0].ToString()
}).ToArray();
The error that ; is missing is bit misleading. The actual problem is that you are trying to create an array of DataTemplate with the loop, You can't use loop in array/object initialization. Try the following.
DataTemplate[] tempDataTemplate = new DataTemplate[ds.Temp.Tables[0].Rows.Count]();
for(int i = 0; i< dsTmp.Tables[0].Rows.Count; i++)
{
tempDataTemplate[i] = new DataTemplate
{
lblText = "Count: ",
txtBoxContent = dsTmp.Tables[0].Rows[i][0].ToString();
};
}
base.DataContext = tempDataTemplate;
i dont know what you wanna achieve, but did you ever try mvvm with viewmodel first approach?
create a viewmodel class, e.g. MyData with 2 public properties MyText, MyContent. create a collection of these objects and fill this from your database.
at least you need an itemscontrol with itemssource binding set to your collection and a datatemplate for your MyData object.
<DataTemplate DataType="{x:Type local:MyData}">
<view:MyDataViewControl />
</DataTemplate>
now you see all your dynamic objects in your itemscontrol.

How do I access my ComboBoxes inside of a GridViewColumn HeaderTemplate?

I found a way to template the embedding of controls within the column headings of a GridView. However I do not know how to find the controls using my code behind to fill them up with items. I have figured out how to respond to an event raised from the embedded control and determine which column it was in. Otherwise I don't know how to get a reference to the embedded ComboBoxes.
Some code to give you an idea:
<Page.Resources>
<DataTemplate x:Key="ComboHeaderTemplate">
<DockPanel>
<ComboBox Name="columnHeading" />
</DockPanel>
</DataTemplate>
</Page.Resources>
And in the code-behind:
GridView grdView = new GridView();
for (int column = 1; column <= context.data.GetLength(1); column++)
{
GridViewColumn gvc = new GridViewColumn();
gvc.DisplayMemberBinding = new Binding(column.ToString());
gvc.Header = column.ToString();
gvc.Width = 120;
gvc.HeaderTemplate = (DataTemplate)this.Resources["ComboHeaderTemplate"];
grdView.Columns.Add(gvc);
}
ListView1.View = grdView;
ListView1.ItemsSource = dt.DefaultView;
If every ComboBox had the same list of items to choose from using data binding that would be fine as long as I could select unique values for each column.
You could use the VisualTreeHelper to retrieve the combo-box:
Create a helper method to find the combo box on the GridViewColumn:
public T FindVisualChild<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
return (T)child;
}
T childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
}
return null;
}
To get a reference to the combo-box then do something like the following:
ComboBox cb = FindVisualChild<ComboBox>(gvc);
Hopefully this is what you are looking for?
Here's what I ended up with.
XAML:
<DataTemplate x:Key="ComboHeaderTemplate">
<DockPanel>
<ComboBox Name="columnHeading" Loaded="columnHeadingLoaded" SelectionChanged="columnHeadingSelectedChanged" Width="Auto" />
</DockPanel>
</DataTemplate>
Code behind:
private void columnHeadingLoaded(object sender, RoutedEventArgs e)
{
((ComboBox)sender).ItemsSource = myList;
((ComboBox)sender).SelectedIndex = 0;
}
// My columns are named "1", "2" etc
private void columnHeadingSelectedChanged(object sender, SelectionChangedEventArgs e)
{
int columnIndex = int.Parse(((ComboBox)sender).DataContext.ToString()) - 1;
if (((ComboBox)sender).SelectedIndex == 0)
{
this.Headings[columnIndex] = null;
}
else
{
this.Headings[columnIndex] = ((ComboBox)sender).SelectedValue.ToString();
}
}
Thought I should use Data Binding in the XAML but this was easier.
You can try this:
var cbx= gvc.HeaderTemplate.LoadContent() as ComboBox;
gvc is GridViewColumn

Categories

Resources