Add programmatically ListViewItem into Listview in WPF - wpf

So i have this Dictionary:
Dictionary<string, double> _statistics;
Ans i want to add this Dictionary into my ListView with 2 columns:
DataTable table = new DataTable();
table.Columns.Add("Name");
table.Columns.Add("Percent");
foreach (KeyValuePair<string, double> item in _statistics)
table.Rows.Add(item.Key, item.Value);
listView.ItemsSource = table;
And got an error:
Cannot implicitly convert type 'System.Data.DataTable' to
'System.Collections.IEnumerable'. An explicit conversion exists (are
you missing a cast?)
Edit
I also try this:
public class MyItem
{
public string Name{ get; set; }
public double Percentage { get; set; }
}
var gridView = new GridView();
ipStatilistViewsticslistView.View = gridView;
gridView.Columns.Add(new GridViewColumn
{
Header = "Name",
DisplayMemberBinding = new Binding("Name")
});
gridView.Columns.Add(new GridViewColumn
{
Header = "Percent",
DisplayMemberBinding = new Binding("Percent")
});
foreach (KeyValuePair<string, double> item in _statistics)
listView.Items.Add(new MyItem { Name = item.Key, Percentage = item .Value});
And in this case all i can see is my 2 columns heards.

ItemsControl.ItemsSource is of IEnumerable type and DataTable does not implement that interface. You need to use DataView instead
listView.ItemsSource = table.DefaultView;
or
listView.ItemsSource = new DataView(table);
EDIT
As for your second example you set view of ipStatilistViewsticslistView and populate listView with items. I'm guessing it's not the same ListView. Also you bind Percent column whilst name of the property is Percentage

Related

WPF DataGrid change auto-generated column

I have a MVVM project with a DataGrid, that uses DataTable with a dynamic number of columns.
That means DataGrid has to auto generate its columns.
I want to change one generated DataGridTextColumn ("Col B" in code, for typeof(PKValue)) to a DataGridComboBoxColumn.
But I can't get it to display column's data.
DataTable is created like this:
while (i < this.ColumnsCount1)
{
dataTable.Columns.Add(new DataColumn("Col A", typeof(bool)));
i++;
dataTable.Columns.Add(new DataColumn("Col B", typeof(PKValue)));
i++;
dataTable.Columns.Add(new DataColumn("Col C", typeof(string)));
i++;
}
IDItem class:
public class PKValue : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}
public int PK { get; set; }
public string Value { get; set; }
}
With this code - DataGrid creates "Col B" as DataGridTextColumn.
Then I use this code, trying to change auto-generated text column to a combobox column:
protected override void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e)
{
if (e.PropertyType == typeof(PKValue))
{
MainViewModel mainViewModel = null;
UserControl userControl = FindParent<UserControl>(this) as UserControl;
Window window = FindParent<Window>(this) as Window;
if (userControl != null && userControl.DataContext.GetType().Equals(typeof(MainViewModel)))
{
mainViewModel = userControl.DataContext as MainViewModel;
}
else if (window != null && window.DataContext.GetType().Equals(typeof(MainViewModel)))
{
mainViewModel = window.DataContext as MainViewModel;
}
if (mainViewModel != null)
{
DataGridComboBoxColumn dataGridComboBoxColumn = new DataGridComboBoxColumn();
Binding bindingItemsSource = new Binding();
bindingItemsSource.Source = mainViewModel.PKValueItemsSource;
BindingOperations.SetBinding(dataGridComboBoxColumn, ItemsSourceProperty, bindingItemsSource);
Binding bindingSelectedItem = new Binding(e.PropertyName);
bindingSelectedItem.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(dataGridComboBoxColumn, SelectedItemProperty, bindingSelectedItem);
dataGridComboBoxColumn.DisplayMemberPath = "Value";
dataGridComboBoxColumn.SelectedValuePath = "PK";
e.Column = dataGridComboBoxColumn;
}
base.OnAutoGeneratingColumn(e);
}
}
DataGrid does change "Col B" to DataGridComboBoxColumn without any warnings, shows ItemsSource properly, but it doesn't display existing value of the field.
When I select an item from a dropdown - it gives a warning "Cannot create default converter to perform 'two-way' conversions between types 'WPFGridMatrix.Models.PKValue' and 'System.String'". And when I create a converter that returns a simple string "abc" - still, nothing is displayed in the column. My guess is that using simply "e.Column = dataGridComboBoxColumn;" is not enough, but how to do that properly?

Binding ListView's GridView to List<KeyValuePair>

Here's how I am making my GridView:
The ListView will contain Entry objects which looks like this:
public class Entry
{
public Entry(BitmapImage icon = null, List<EntryKeyValuePair> entryKeyValuePairs = null)
{
Icon = icon;
EntryKeyValuePairs = entryKeyValuePairs ?? new List<EntryKeyValuePair>();
}
public BitmapImage Icon { get; set; }
public List<EntryKeyValuePair> EntryKeyValuePairs { get; }
}
EntryKeyValuePair is just a KeyValuePair<string,string> where Key is the Column and Value is the value of the column. I used a List of KeyValuePair because I want to preserve insertion order. Anyway, here's how I am constructing the GridView.
GridView = new GridView();
foreach (Column column in Category.Columns.Where(c => c.IsVisibleInTable)) {
var gridViewColumn = new GridViewColumn {
Header = column.Name,
DisplayMemberBinding = new Binding($"EntryKeyValuePairs[{column.Name}].Value")
};
GridView.Columns.Add(gridViewColumn);
}
I don't know what binding to set in DisplayMemberBinding. The above binding would work if EntryKeyValuePairs was a dictionary. But in my case it is not.
If I had access to the data object somehow, I could do
DisplayMemberBinding = new Binding($"EntryKeyValuePairs[{entry.EntryKeyValuePairs.FindIndex(p => p.Key == column.Name)}].Value")
How can I access the current Data Object which the ListView is holding while binding?
I found a solution. I used the GridViewColumn's CellTemplateSelector so that I can get a reference to the ListViews bound object. Here is how the CellTemplateSelector looks like. I had to create the DataTemplates in code.
class GridViewCellTemplateSelector : DataTemplateSelector
{
private readonly string _columnName;
public GridViewCellTemplateSelector(string columnName)
{
_columnName = columnName;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var entry = (Entry)item;
var dataTemplate = new DataTemplate {
DataType = typeof (Entry)
};
var stackPanelFactory = new FrameworkElementFactory(typeof(StackPanel));
stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Vertical);
var text = new FrameworkElementFactory(typeof(TextBlock));
text.SetBinding(TextBlock.TextProperty, new Binding($"EntryKeyValuePairs[{entry.EntryKeyValuePairs.FindIndex(p => p.Key == _columnName)}].Value"));
stackPanelFactory.AppendChild(text);
dataTemplate.VisualTree = stackPanelFactory;
return dataTemplate;
}
}
Instead of DisplayMemberBinding, I used this TemplateSelector:
CellTemplateSelector = new GridViewCellTemplateSelector(column.Name)
All good. Hope this helps someone :) I still hope to see a better solution than this.

wpf datagrid row Foreground in code behind

I don't want to use MVVM and want to change the selected row Foreground on my datagrid in code behind (in SelectionChanged EventHandler function), but I can't find the solid way.
My row can be black, blue and red, but shows the color with a higher priority based on some condition. After selecting the current row I should remove, f.e. black color from my priority list.
I have some class:
public class TempClass{ public string cell1 { get; set; }; public string cell2 { get; set; };}
and
TempClass[] collection;
bound with my datagrid:
datagrid.ItemsSource = collection;
Any idea?
var rowStyle = new Style {TargetType = typeof (DataGridRow)};
rowStyle.Setters.Add(new Setter(ForegroundProperty, Brushes.Green));
var rowTrigger = new Trigger {Property = DataGridRow.IsSelectedProperty, Value = true};
rowTrigger.Setters.Add(new Setter(ForegroundProperty, Brushes.Red));
rowTrigger.Setters.Add(new Setter(BackgroundProperty, Brushes.Orange));
rowStyle.Triggers.Add(rowTrigger);
var cellStyle = new Style {TargetType = typeof (DataGridCell)};
var cellTrigger = new Trigger {Property = DataGridCell.IsSelectedProperty, Value = true};
cellTrigger.Setters.Add(new Setter(ForegroundProperty, Brushes.Red));
cellTrigger.Setters.Add(new Setter(BackgroundProperty, Brushes.Orange));
cellStyle.Triggers.Add(cellTrigger);
datagrid.RowStyle = rowStyle;
datagrid.CellStyle = cellStyle;

DataGrid 'EditItem' is not allowed for this view when dragging multiple items

I have a datagrid which gets data like this:
public struct MyData
{
public string name { set; get; }
public string artist { set; get; }
public string location { set; get; }
}
DataGridTextColumn col1 = new DataGridTextColumn();
col4.Binding = new Binding("name");
dataGrid1.Columns.Add(col1);
dataGrid1.Items.Add((new MyData() { name = "Song1", artist = "MyName", location = "loc"}));
dataGrid1.Items.Add((new MyData() { name = "Song2", artist = "MyName", location = "loc2"}));
The problem is- whenever a user tries to edit a cell or drags multiple cells- the app throws an exception:
System.InvalidOperationException was unhandled
Message: 'EditItem' is not allowed for this view.
Why is this? Is it because of the way the data is entered?
Any ideas?
Thanks!
I got this issue when assigning ItemsSource to IEnumerable<T>.
I fixed it by converting the IEnumberable<T> to a List<T> and then assigning that to ItemsSource.
I'm not sure why using IEnumerable caused that issue, but this change fixed it for me.
Instead of using a struct use a class instead.
UPDATED ANSWER: Try adding your MyData instances to a List then assigning that list to the DataGrid.ItemsSource
If you use datagrid DataGridCheckBoxColumn you need to set <Setter Property="IsEditing" Value="true" />
on check box column. See this: https://stackoverflow.com/a/12244451/1643201
This answer is not my own, just the working code example suggested by AnthonyWJones.
public class MyData //Use class instead of struct
{
public string name { set; get; }
public string artist { set; get; }
public string location { set; get; }
}
DataGridTextColumn col1 = new DataGridTextColumn();
col4.Binding = new Binding("name");
dataGrid1.Columns.Add(col1);
dataGrid1.Items.Add((new MyData() { name = "Song1", artist = "MyName", location = "loc"}));
dataGrid1.Items.Add((new MyData() { name = "Song2", artist = "MyName", location = "loc2"}));
//Create a list of MyData instances
List<MyData> myDataItems = new List<MyData>();
myDataItems.Add(new MyData() { name = "Song1", artist = "MyName", location = "loc"});
myDataItems.Add(new MyData() { name = "Song2", artist = "MyName", location = "loc2"});
//Assign the list to the datagrid's ItemsSource
dataGrid1.ItemsSource = items;
For my case,
processLimits.OrderBy(c => c.Parameter);
returns an
IOrderedEnumerable<ProcessLimits>
not a
List<ProcessLimits>
so when I assign a style for my event setter to a checkbox column in my datagrid
style.Setters.Add(new EventSetter(System.Windows.Controls.Primitives.ToggleButton.CheckedEvent, new RoutedEventHandler(ServiceActiveChecked)));
ServiceActiveChecked is never called and I got
'EditItem' is not allowed for this view.
and for anyone else doing checkboxes in datagrid columns, I use a column object with my column data in this constructor for adding the data grid I use with adding the style above.
datagridName.Columns.Add(new DataGridCheckBoxColumn()
{
Header = column.HeaderText.Trim(),
Binding = new System.Windows.Data.Binding(column.BindingDataName.Trim()) { StringFormat = column.StringFormat != null ? column.StringFormat.Trim().ToString() : "" },
IsReadOnly = column.IsReadOnlyColumn,
Width = new DataGridLength(column.DataGridWidth, DataGridLengthUnitType.Star),
CellStyle = style,
});
I solved this by setting the datagrid's source after the InitializeComponent:
public MainWindow()
{
InitializeComponent();
FilterGrid.ItemsSource = ScrapeFilter;
}

Add a bound ComboBox column to a DataGridColumn at runtime

I'd like to display a DataGrid which will contain data I've uploaded from a data source into a DataTable. The columns will be different each time and some will need to be represented using a ComboBox.
How do I go about setting the DataGridTemplateColumn for a column, that needs a ComboBox, at runtime?
Ok, this is the closest I've got with the help of #Meleak, almost there, just displaying the key rather than the value when the grid is not being edited.
public partial class MainWindow : Window
{
public Dictionary MyDictionary { get; set; }
public MainWindow()
{
InitializeComponent();
//Init Dictionary
this.MyDictionary = new Dictionary<int, string>();
this.MyDictionary.Add(1, "Value 1");
this.MyDictionary.Add(2, "Value 2");
this.MyDictionary.Add(3, "Value 3");
DataTable dt = new DataTable();
DataColumn column = new DataColumn("MyTypeId", typeof(int));
dt.Columns.Add(column);
DataRow newRow = dt.NewRow();
newRow["MyTypeId"] = 1;
dt.Rows.Add(newRow);
dataGrid.Columns.Add(GetNewComboBoxColumn("My Type", "MyTypeId", this.MyDictionary));
this.DataContext = dt;
}
public static DataGridTemplateColumn GetNewComboBoxColumn(string header,
string bindingPath,
object itemsSource)
{
DataGridTemplateColumn comboBoxColumn = new DataGridTemplateColumn();
comboBoxColumn.Header = header;
Binding textBinding = new Binding();
textBinding.Path = new PropertyPath(bindingPath);
FrameworkElementFactory textBlock = new FrameworkElementFactory(typeof(TextBlock));
textBlock.SetValue(TextBlock.MarginProperty, new Thickness(3, 3, 3, 3));
textBlock.SetBinding(TextBlock.TextProperty, textBinding);
FrameworkElementFactory comboBox = new FrameworkElementFactory(typeof(ComboBox));
comboBox.SetValue(ComboBox.MarginProperty, new Thickness(1, 1, 1, 1));
comboBox.SetBinding(ComboBox.TextProperty, textBinding);
comboBox.SetValue(ComboBox.SelectedValuePathProperty, "Key");
comboBox.SetValue(ComboBox.DisplayMemberPathProperty, "Value");
Binding itemsSourceBinding = new Binding();
itemsSourceBinding.Source = itemsSource;
comboBox.SetBinding(ComboBox.ItemsSourceProperty, itemsSourceBinding);
comboBoxColumn.CellTemplate = new DataTemplate();
comboBoxColumn.CellTemplate.VisualTree = textBlock;
comboBoxColumn.CellEditingTemplate = new DataTemplate();
comboBoxColumn.CellEditingTemplate.VisualTree = comboBox;
return comboBoxColumn;
}
}
Update
You can use a DataGridComboBoxColumn and set SelectedValueBinding to get it to work the way you want. So just change the method you have to this one and it should work
public DataGridColumn GetNewComboBoxColumn(string header,
string bindingPath,
object itemsSource)
{
DataGridComboBoxColumn comboBoxColumn = new DataGridComboBoxColumn();
comboBoxColumn.Header = header;
comboBoxColumn.SelectedValuePath = "Key";
comboBoxColumn.DisplayMemberPath = "Value";
Binding binding = new Binding();
binding.Path = new PropertyPath(bindingPath);
comboBoxColumn.SelectedValueBinding = binding;
Binding itemsSourceBinding = new Binding();
itemsSourceBinding.Source = itemsSource;
BindingOperations.SetBinding(comboBoxColumn, DataGridComboBoxColumn.ItemsSourceProperty, itemsSourceBinding);
return comboBoxColumn;
}

Resources