I have a requirement to generate a "report" in WPF which is simply a grid.
However, the columns and the styling rules (e.g. "Red if value below zero") for this grid are unknown at compile time.
There are hundreds of questions like this and I must have read over half of them but I cannot find a solution to this requirement which would be trivial in WinForms.
I have managed to style an entire row by setting the ItemContainerStyle of the ListView, but I couldn't manage to get this to focus on a single cell.
As such I'm now trying the CellTemplate approach but this throws an error ({"Child with Name '{x:Type ListViewItem}' not found in VisualTree."}) and of course when I use a DisplayMemberBinding the CellTemplate isn't even called at all.
My converter when it gets passed the value is getting the entire row, not just the cell's value, so perhaps this is useful information.
GridView viewLayout = new GridView();
for (int i=0; i<columns.Length; i++)
{
ColumnDisplaySettings col = columns[i];
var g = new GridViewColumn()
{
Width = col.Width,
//DisplayMemberBinding = "[" + i + "]" /* Have to omit this for CellTemplate */
};
if (i == 0)
{
g.CellTemplate = new DataTemplate();
var t = new DataTrigger();
t.Binding = new Binding("[0]");
t.Value = "0";
var b = new Binding() { Converter = new MyBkColorConverter() };
t.Setters.Add(new Setter(Control.BackgroundProperty, b,
"{x:Type ListViewItem}")); /* Error here */
g.CellTemplate.Triggers.Add(t);
}
viewLayout.Columns.Add(g);
}
lv.View = viewLayout;
I've encountered DataTemplateSelectors in my searches, so if a useful reference exists on using these without any known XAML then I'd appreciate that too.
Thanks for any help you can provide.
The error is happening because "{x:Type ListViewItem}" is a string. The {x:..} notation is a XAML markup extension, but you're not using XAML. To refer to the list ListViewItem in code, use typeof(ListViewItem).
Oh also, you're trying to set the Background property to a type, ListViewItem, which makes little sense.. let me re-read and update this answer..
Update: you don't need the third parameter to the Setter constructor.
Hope that helps!
Using the WPF Toolkit's DataGrid is one solution to this. (Comment if more details required).
Related
I am using DataGrid view in WPF which is inserted by the following XAML Code
<DataGrid x:Name="DVA_Zyklus_DataGrid" IsManipulationEnabled="True"/>
</Grid>
Now I add a column in my C# Code
DataGridTextColumn c0 = new DataGridTextColumn();
c0.Header = "Test";
c0.Binding = new Binding("TimeToRamp");
c0.Width = 110;
DVA_Zyklus_DataGrid.Columns.Add(c0);
and a line
DVA_Zyklus_DataGrid.Items.Add(new DVA_Zyklus_DataGrid_Single_Pump_Item() { TimeToRamp = 0.0});
where
public class DVA_Zyklus_DataGrid_Single_Pump_Item
{
public double TimeToRamp { get; set; }
}
This works fine and results in one cell with value 0. Now the user should be able to change this value. How ever Selecting the cell and pressing any key results in crashing of the programm.
Exception Type System.InvalidOperationException, Additional Information EditItem is not allowed.
Any idea of howto to allow the user to do this?
You should set the ItemsSource of the DataGrid, instead of inserting items to the Items property:
var list = new ObservableCollection<DVA_Zyklus_DataGrid_Single_Pump_Item>();
list.Add(new DVA_Zyklus_DataGrid_Single_Pump_Item() { TimeToRamp = 0.0 });
dg.ItemsSource = list;
I think its better to follow MVVM approach to fill the DataGrid. There are lots of posts regarding it, such as this one.
So, I have a DataGrid, that I want to manipulate programmatically a lot.
string[] values = new string[something.Count];
for (int i = 0; i < somethingElse.Count; i++)
{
if (condition)
values[i] = Data[i].ToString();
else
values[i] = "";
}
var style = new System.Windows.Style(typeof(DataGridRowHeader));
style.Setters.Add(new Setter(DataGridRowHeader.ContentProperty, Data[0].ToString()));
MyGrid.Items.Add(new DataGridRow()
{
HeaderStyle = style,
Item = values
});
This I do in a loop and I am able to fill in my grid with all the data I need.
Later, I am able to access cells, edit them, take their values, whatever I want and need.
However, when user wants to use the grid as you would in MS Excel, the cells are not editable.
So I went the other way and created a :
ObservableCollection<ObservableCollection<string>> gridData = new ObservableCollection<ObservableCollection<string>>();
//*** ... *** the gridData is filled in the same way, you can imagine
MyGrid.ItemsSource = gridData;
This does fill in the data perfectly the same way, more than that, the data are now editable.
But my custom row headers disappeared.
I need them, also, I do not think I want to use binding for row header values.
Can the first approach be somehow modified to still be editable, but rows with data being filled the very same way?
I eventually solved this combining both the abovementioned approaches.
First, I generate ObservableCollection<ObservableCollection<string>> dataCollection, fill it with data.
Next I generate an ObservableCollection<DataGridRow> rowCollection collection.
In my declarative part of the loop from the first approach, that you can see in the question, I do this
rowCollection.Add(new DataGridRow()
{
HeaderStyle = style,
Item = dataCollection[i] //for the corresponding iteration element
});
This all ends up when setting
MyGrid.ItemsSource = rowCollection;
Till now (very little time of testing), this seems to be what I was looking for, hope it might help someone else aswell.
I have this problem for days and can't find solution for it. I tried all possible solutions i found on internet, but seems like none suits this one.
Thing is that i added repository item to gridControls (i added it through designer, not through code). Then, in code i added data source to that repository lookUpEdit and i have items in dropDown in that column. But when i select item in repository and click on other cell, Selected item in repository is cleared and repository shows null value again...
Any ideas what i did wrong ?
EDIT:
Also, when i click on any cell in my grid, i have delay of second or two, and after that delay clicked cell is focused... Any solutions for all of this?
EDIT:
Don't know what code to show You, because I did all in devExpress designer. Here is part of the code where I set data source to repository item, and i will give You code from designer of that repository item.
private void ConfigureRepositoryItems()
{
BetService.SportManagerClient dbSportManager = new BetService.SportManagerClient();
BetService.BLOddsControlSettings[] oddsControlSettings = dbSportManager.GetOddsControlSettings("", "");
repositoryOddsControlSettings1.DataSource = oddsControlSettings;
}
And here is code from designer:
//
// repositoryOddsCalculationSettings1
//
this.repositoryOddsCalculationSettings1.AutoHeight = false;
this.repositoryOddsCalculationSettings1.Buttons.AddRange(new DevExpress.XtraEditors.Controls.EditorButton[] {
new DevExpress.XtraEditors.Controls.EditorButton(DevExpress.XtraEditors.Controls.ButtonPredefines.Combo)});
this.repositoryOddsCalculationSettings1.Columns.AddRange(new DevExpress.XtraEditors.Controls.LookUpColumnInfo[] {
new DevExpress.XtraEditors.Controls.LookUpColumnInfo("ID", "ID", 20, DevExpress.Utils.FormatType.None, "", false, DevExpress.Utils.HorzAlignment.Default),
new DevExpress.XtraEditors.Controls.LookUpColumnInfo("Name", "Name")});
this.repositoryOddsCalculationSettings1.DisplayMember = "Name";
this.repositoryOddsCalculationSettings1.Name = "repositoryOddsCalculationSettings1";
this.repositoryOddsCalculationSettings1.NullText = "Select Settings";
this.repositoryOddsCalculationSettings1.PopupSizeable = false;
this.repositoryOddsCalculationSettings1.ValueMember = "ID";
For starters check whether the column name in your Grid datasource and the column in your grid control match. The match is case sensitive so name and Name are not same and hence can cause this issue. Secondly make sure the Grid datasource column datatype matches the value type of the LookUpEdit. If the LookupEdit is returning int and the Grid datasource column datatype is string, this alone can cause lots of headaches.
I'm creating a DataGrid programmatically and have to support ComboBoxColumns, too.
After I create DataGrid, I set it's ItemSource to a collection of a collection of type BindableList<BindableDictionary>. BindableDictionary is a custom type. Each BindableDictionary represents one tuple. It's key is always the name of a column and it's value is a custom class that contains a generic property called ActualValue, a Dictionary<T, string> called AllowedValues and a boolean that determines if AllowedValues will be used to build a ComboBoxColumn or a 'normal' column. Also that class implements INotifyPropertyChanged and INotifyPropertyChanging.
That stuff works, aside from the ComboBoxColumn, duh. My problem with the ComboBoxColumn is that I don't know how to get it to use the AllowedValues object to fill it's ItemList and use the ActualValue property to select the correct Value from the AllowedValues BindableDictionary to fill the textarea.
As an example, this is how I bind a textbased column:
table.Columns.Add(new DataGridTextColumn() { Header = column.GUIName, DisplayIndex = column.Position, Binding = new Binding(column.Name + ".ActualValue") { UpdateSourceTrigger = UpdateSourceTrigger.Default, Mode = BindingMode.TwoWay, NotifyOnTargetUpdated = true, NotifyOnSourceUpdated = true, UpdateSourceExceptionFilter = new UpdateSourceExceptionFilterCallback(BindingExceptionHandler) } });
And yes, that works.
I tried to set the ItemsSource property of the DataGridComboBoxColumn to column.AllowedValues and set the DisplayPath to "Value" which does at least display the correct content, but I've no clue how to bind to the ActualValue property that is contained in the DataGrid's ItemsSource. Also that'd mean that all cells inside a column share the same selectable values, which could lead to problems in the future.
And if I try to bind everything like I did in the DataGridTextColumn, nothing is displayed at all. Also there are no items to select.
It'd be awesome if someone has so much as a hint of what I could try.
edit
Just saw this: https://stackoverflow.com/a/2197004/937093, I tried that but then I get the following message in my output window:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=AllowedValues; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=33493530); target property is 'ItemsSource' (type 'IEnumerable')
My code looks like this:
col = new DataGridComboBoxColumn() { Header = column.GUIName, SelectedValueBinding = new Binding(column.Name + ".ActualValue"), SelectedValuePath = "ActualValue" };
table.Columns.Add(col);
BindingOperations.SetBinding(col, DataGridComboBoxColumn.ItemsSourceProperty, new Binding("AllowedValues"));
edit 2
okay, found this website: http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
I tried to apply the proxy binding stuff (even though I don't get how the Column is not part of the visual tree of the DataGrid...where else would it be?!) but it won't work. My code:
BindingProxy proxy = new BindingProxy() { Data = table.ItemsSource };
table.Resources.Add("proxy", proxy });
col = new DataGridComboBoxColumn() { Header = column.GUIName, SelectedValueBinding = new Binding("Data." + column.Name + ".ActualValue") { Source = proxy }, DisplayMemberPath = "Value", SelectedValuePath = "Key" };
table.Columns.Add(col);
BindingOperations.SetBinding(col, DataGridComboBoxColumn.ItemsSourceProperty, new Binding("Data." + column.Name + ".AllowedValues") });
Output in output window:
System.Windows.Data Error: 40 : BindingExpression path error: 'MyColumn'
property not found on 'object' ''BindingList`1' (HashCode=55207835)'.
BindingExpression:Path=Data.MyColumn.ActualValue;
DataItem='BindingProxy' (HashCode=45660050); target element is
'TextBlockComboBox' (Name=''); target property is 'SelectedValue'
(type 'Object')
I understand the problem (it's trying to find the 'MyColumn' object in the BindingList) but I don't understand why that is happening (it should resolve to BindingList[iterator]["MyColumn"], since BindingList contains the BindableDictionary and that's exactly what happens for my 'normal' columns).
I did something similar. I was using Winforms, so my solution may not work for you. However I'd suggest using something similar to this.
http://tech.pro/tutorial/776/csharp-tutorial-binding-a-datagridview-to-a-collection
Coupled with this.
http://www.codeproject.com/Articles/31418/Implementing-a-Sortable-BindingList-Very-Very-Quic
As I had everything working, then found I couldn't sort my datagridview.
I don't have my sourcecode handy, but the general idea was I'd create my comboboxes and textbox columns by hand, then bind my list to it.
After I'd run a function that'd iterate through each row and based on the index ( in my case my comboboxes were the last 3 columns ) I'd add the values for the combobox by hand first, then after those are added, I'd check the value of the combobox and set it to that.
I also included this function in the data error event, for when I added a new column. Also it would randomly crash if I was adding a new row, if I had the autosize set. I had to set these to default, do my editing, then reset it.
Wish I had code to provide, but it might put you on your way. If not when I go in to work tomorrow I'll post some. Comboboxes are a huge pain to work with.
I have a DataGridView that uses databinding, with manually created columns, and this works fine.
However I want the rows' BackColor to be databound as well, and so far my attempts have hit errors.
This is my latest attempt:
dataGridFileTransfer.RowHeadersVisible = false;
dataGridFileTransfer.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dataGridFileTransfer.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dataGridFileTransfer.MultiSelect = false;
dataGridFileTransfer.ReadOnly = true;
var files = GetReceivedFiles(false).Union(FileDetails.Select(FileStatus.FailedVerification)).ToList();
dataGridFileTransfer.AutoGenerateColumns = false;
string[] displayHeaders = new string[] { COL_NAME, COL_TYPE, COL_CREATED, COL_SIZE, COL_STATUS };
string[] displayProps = new string[] { "Filename", "FileTypeDisplayName", "Created", "Size", "FileStatus" };
for (int i = 0; i < displayHeaders.Length; i++)
{
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.HeaderText = displayHeaders[i];
col.DataPropertyName = displayProps[i];
if (displayHeaders[i] == COL_CREATED)
col.DefaultCellStyle.Format = Constants.DDMMYYYHHMMSS;
dataGridFileTransfer.Columns.Add(col);
}
Binding bi = new Binding("DefaultCellStyle.BackColor", files, "DisplayColor");
dataGridFileTransfer.DataBindings.Add(bi);
dataGridFileTransfer.DataSource = files;
Which is generating an ArguementException:
"Cannot bind to the property
"DefaultCellStyle.BackColor' on the
target control. Parameter name:
PropertyName"
Is it the value of PropertyName that is wrong, or should I binding to an object other than the DataGridView? (i.e. a column?)
Or is the problem that PropertyName cannot be in the form X.Y? I thought I had seen/used this syntax before, but maybe it only works for DataMember?
Any help is much appreciated
I think the problem is files.DisplayColor. files is a collection an has no property DisplayColor but each item of the collection has. So you are trying to bind a not existing property. Further binding collection DataGridView.DataBindings allows you to data bind properties of the control, not of its rows. There is only one DataGridView.DefaultCellStyle.BackColor for all rows. So I believe you end up needing to bind the DefaultCellStyle of each row to the coresponding item from files and I am not sure if this is possible. It might be that the DataGridView creates and deletes rows as required - for example if you perform filtering - and this will destroy the data binding, too.
So, I am not sure if row coloring can be done with data binding, but I personaly doubt it. This would require some really smart logic recognicing 'bind the property DisplayColor of the object data bound to this row to the property DefaultCellStyle.BackColor of this row.'
You could surly implement such an smart data binding. While it would be a great thing, it will be quite complex, too. As a simple solution you could just use the RowPrepaint event to set the correct color for the row.