I'm currently trying to build a DataGridView in a Windows Form to display to a user a list of settings that they can turn on and off. The DatGridView will have two columns, the first column will describe the setting and the second column will containg a check box allowing the user to turn the setting on or off. So for example the DataGridView would look like:
| Descriptions | Set |
---------------------------------
| Description 1 | true |
| Description 2 | false |
| Description 3 | false |
...
In my project settings I currently have a list of booleans, Pref1, Pref2, Pref3 etc which I'd like to bind to the CheckBoxes within the DataGridView so that they can be manipulated automatically without me having to do any manual checks whenever a cells value has been edited and so I can save the changes between different instances of the application.
I tried searching for a few solutions and came across the following and added it to the forms constructor:
// Build preference dictionary
Dictionary<String, bool> Preferences = new Dictionary<String, bool>();
preferences.Add("Description 1", Settings.Default.Pref1);
preferences.Add("Description 2", Settings.Default.Pref2);
....
// Copy dictionary to list
List<KeyValuePair<String, bool>> PreferenceList = new List<KeyValuePair<String, bool>>();
foreach (KeyValuePair<String, bool> item in Preferences)
PreferenceList.Add(item);
// Set the GridView DataSource and values displayed in each column
GridView.AutoGenerateColuns = false;
GridView.DataSource = new BindingList<KeyValuePair<String, bool>>(PreferenceList);
GridView.Columns[0].DataPropertyName = "Key";
GridView.Columns[1].DataPropertyName = "Value";
When the form loads the DataGridView populates as expected but the CheckBoxes in the second column can't be manipulated. After some debugging I've notcied the second column becomes ReadOnly when I set the DataPropertyName to "Value" and I can't change this ReadOnly setting without throwing an exception.
Is there any way around this readonly issue? I've also read and thought about creating my own Preference class something along the lines of:
public Class Preference
{
public String Description { get, set }
public bool Selected { get, set }
}
And then creating an array of these preferences, binding each Preference's 'Selected' property to one of the booleans in my Settings class and then setting the Preference array as the DataGridViews DataSource. Is this a viable solution or is there maybe another alternative that I'm not considering/aware of?
Sorry for the essay of a question but I just like to try and explain everything so theres no confusion in what I'm asking :)
Thanks in advance.
GridView.DataSource = Preferences
.Select(p => new Preference {Description = p.Key, Selected = p.Value})
.ToList();
GridView.Columns[0].DataPropertyName = "Description";
GridView.Columns[1].DataPropertyName = "Selected";
Related
I have datagridview in a Windows form that shows content of table of database,
one column of table type is boolean, so in datagridview shows true/false,
but i want to customize it to show Icon or image like Accepted, or Rejected icons. Those Icons I have them in my Project Resources
This is my code
var cars = (from u in db.Cars
..........
select new
{
.....
.....
Approved = u.Treated == true ? Resources.approved : Resources.Cancel // Here Not working
}).ToList();
if (cars != null)
{
dgvCars.DataSource = null;
dgvCars.DataSource = cars;
}
In Approved cell I want to show one of those icons, depending true or false. I did as you see in my code
Approved = u.Treated == true ? Resources.approved : Resources.Cancel
but not working. Maybe I have to code somthing in my Fomatingcell events but I have no idea how to do that. Please help!
DataGridViewImageColumn is able to display images.
By default DataGridView automatically generates columns based on value type. DataGridViewImageColumn will be generated by default if value is array of bytes (byte[]).
In your case the type is different. You can solve in two ways
First:
Convert images to byte arrays.
Second:
Create image column manually and bind it to the image property.
// Execute it once when initializing datagridview
var approvedColumn = new DataGridViewImageColumn
{
Name = "dgvCars_Approved",
HeaderText = "Approved",
DataPropertyName = "Approved", // This will bound column to the property of the object
};
dgvCars.Columns.Add(approvedColumn);
// DataGridView will automatically pick up correct image
var cars = (
from u in db.Cars
..........
select new
{
.....
.....
Approved = u.Treated == true ? Resources.approved : Resources.Cancel
}).ToList();
// Check for null is redundant, because ToList() never returns null
// Because cars is a new instance
// set .DataSource to null before actual value is redundant as well
dgvCars.DataSource = cars;
Second approach will mess up a little bid with autogenerated columns, but I would suggest to create all required columns manually via designer or manually with code.
Predefined columns will provide more control over view behaviour and flexibility.
I have 2 listBoxes with BindingLists as their data sources. The idea is to make a List builder (as MSDN names it), where first listBox shows currently added columns and second listBox shows the rest of available columns. First list contains ViewColumn objects, while the other list contains strings.
I load chosen columns into first listBox from database and then I want to load the rest of available columns into the second listBox (the list itself comes from another place in database). Considering there are no limits on the number of columns, I want to do that in the fastest way possible - what would that be?
Here's some code to visualize that:
ViewTable _view;
BindingList<ViewColumn> _viewColumns = new BindingList<ViewColumn>();
BindingList<string> _detailsColumns = new BindingList<string>();
void CustomInitialize()
{
_view = //get view and its columns
_viewColumns = new BindingList<ViewColumn>(_view.Columns);
listBox_CurrentColumns.DataSource = _viewColumns;
listBox_CurrentColumns.DisplayMember = "Name";
var detailsTable = //get the list of available columns
foreach (var row in detailsTable)
{
//TODO: if _viewColumns does not contain this value, add it to _detailsColumns
_detailsColumns.Add(row.ColumnName);
}
listBox_AvailableColumns.DataSource = _detailsColumns;
}
I think you want to do something like:
_detailsColumns = _allColumns.Except(_viewColumns.Select(c => c.Name))
This should get you all entries in the _allColumns collection excluding the entries in the _viewColumns collection.
I assume here that _allColumns contains the overall collection of possible columns.
Consider the following text as two lists(separated with ':'), as you can see the second rows are always unique but items in the first row can be repetitive;
Book:m234
Clover:h67
Pencil:a12
Book:x67
I want to populate a listbox with items in the first column(Book, Clover, ...) but the problem is that when I'm going to retrieve the selected item in the listbox, I can't be sure about it's respective value in second column. (for example in case of 'Book');
NOTE: I'm not looking for any workaround for solving this problem because there are many. What I want to know is that:
Is it possible to pass and object to ListBox.Items.Add() in a way that the object carries two values(each value/property for each column) and in time of getting the selected item, we would have an object with the two values(maybe as the properties of the object)?
Is such a thing possible in C#? (.NET 4.5)
You can pass objects that pair data with names to your ListBox, and control what gets displayed and what gets returned back to you by using DisplayMember and ValueMember:
class ListBoxItem {
public string DisplayName {get;set;}
public string Identifier {get;set;}
}
...
ListBox.Items.Add(new ListBoxItem {
DisplayName = "Book", Identifier = "m234"
});
ListBox.Items.Add(new ListBoxItem {
DisplayName = "Clover", Identifier = "h67"
});
ListBox.Items.Add(new ListBoxItem {
DisplayName = "Pencil", Identifier = "a12"
});
ListBox.Items.Add(new ListBoxItem {
DisplayName = "Book", Identifier = "x67"
});
ListBox.DisplayMember = "DisplayName";
ListBox.ValueMember = "Identifier";
Now your list box displays the same list of strings, but the values returned for end-user selections would be different.
We've got problem with filtering for some of our columns in devexpress gridcontrol. We add the column dynamically (bound-type column) to the grid. The values from the source objects are long type. In the cells it seems that values are fine (since they're aligned to the right without any custom formating on our side) however in filter popup values behave like strings.
For example data set like 1,2,5,11,22,37 the filter list is sorted like 1,11,2,22,5,37 (just like strings) and when we choose one of the available values the filtering does not work (i mean, grid becames empty). Even filters like "Non empty cells" does not work, but when we choose "empty cells" only few from few thousand rows are shown even if most of the cells have no values.
It is important to point out that only dynamically added columns behave that way, the few columns we create every time our module runs work as intended.
The data source is a container (List like).
We're using DevExpress 13.2.
Example of creating 'custom column'
void CreateColumn(GridColumn gridColumn, string fieldName = null, string caption = null, bool visible = true,
bool readOnly = true, UnboundColumnType unboundType = UnboundColumnType.Bound,
int columnWidth = int.MinValue, int minColumnWidth = int.MinValue)
{
gridColumn.Caption = caption;
if (fieldName != null)
gridColumn.FieldName = fieldName;
gridColumn.Visible = visible;
gridColumn.OptionsColumn.ReadOnly = readOnly;
gridColumn.OptionsColumn.AllowEdit = !readOnly;
gridColumn.UnboundType = unboundType;
gridColumn.OptionsFilter.AllowAutoFilter = true;
gridColumn.FilterMode = ColumnFilterMode.Value;
gridColumn.OptionsFilter.AutoFilterCondition = DevExpress.XtraGrid.Columns.AutoFilterCondition.Contains;
if (columnWidth != int.MinValue)
{
gridColumn.Width = columnWidth;
gridColumn.OptionsColumn.FixedWidth = true;
}
if (minColumnWidth != int.MinValue)
gridColumn.MinWidth = minColumnWidth;
}
GridColumn gridColumn = new GridColumn();
CreateColumn(gridColumn, "someName", "someCaption", true, true);
View.Columns.Add(newGridColumn);
That's how it goes in our code (striped most of not related code just to give example process).
#edit
There's invalid cast exception when we add filter like this:
ColumnFilterInfo filter = GetFilter(); //cant really post code of this
ourGrid.MainView.ActiveFilter.Add(column, filter); // VS points here
Unfortunately it doesnt say what and where (except some 'lambda expression') exception is being thrown.
Of course column is the column mentioned above.
#edit
I've found new "tip". The FilterItem objects contain strings for sure, however they should contain long values. How can we influence the creation of these or atleast where to check why those are created like that (we dont do it manually)?
#Edit 19.11.2015
Alright, I had some breakthrough. The columns ('custom') thanks to our mechanism guess their type just fine. Then only problem is that infact our values which custom columns use are stored in Dictionary<string,object>-like collection and we think that thanks to PropertyDescriptor type for columns is fine, but for some reason FilterItem objects have Value of string. We belive that's because DX filter mechanism can't really guess type of "object" so it uses simple ToString on it so FilterItem.Value is string type instead column's data type.
How to overcome this?
We've found the solution and the mistake was on our side. Column creation etc is fine. Somewhere deep, someone has changed value types.
At first, I want to talk a little about normal binding (with standard property such as Text of a textbox). The sample grid here has only 2 rows (for simplicity). Suppose I have a table of 2 rows (myDataTable) with 2 columns (ID and Name), a DataGridView (myGrid) and a TextBox (myTextBox). Here is the code for binding data:
myGrid.DataSource = myDataTable;
myTextBox.DataBindings.Add("Text", myDataTable, "Name");
After binding data, when the selection changes in the grid, the info is updated automatically to the control TextBox, for example, the 2 rows are:
ID | Name
1 .NET
2 Java
At first, the selection in grid is at index 0, the Text of myTextBox is ".NET", moving the selection to next position (at index 1), the Text of myTextBox is "Java", moving again and again, forward and backward, it works OK as I expect. But now I have a control with a custom property called List, this is type of List and is readonly. I want to bind it to a column of table (for example, Name), I do the same binding rule, however add a little custom Parse to format the correct string before updating to the datasource (myDataTable) because my custom property is type of List while my Name column is type of string, here is the binding code:
Binding bind = new Binding("List", myDataTable, "Name"){
ControlUpdateMode = ControlUpdateMode.Never //Because my List property is readonly
};
//formating string data before updating to the datasource
bind.Parse += (s,e) => {
List<string> data = (List<string>) e.Value;
if(data.Count == 0) e.Value = DBNull.Value;
else e.Value = string.Join(",",data.ToArray());//format as comma separated string
};
myCustomControl.DataBindings.Add(bind);
In this case, suppose myDataTable currently has no data at column Name like this:
ID | Name
1 <DBNull.Value> <--- current index
2 <DBNull.Value>
After running the demo, the current selection index in the grid is 0, I try changing the value of myCustomControl property List (the Items, not the reference), for example, update it like this:
myCustomControl.List.Add(".NET");
myCustomControl.List.Add("Java");
Then, moving the selection in grid to the next position (index 1), the value ".NET,Java" is updated to the datasource in row 0 at column Name, like this:
ID | Name
1 .NET,Java
2 <----- current index
Now if I move the selection back to index 0, the value at column Name in row 1 is also updated to ".NET,Java" like this:
ID | Name
1 .NET,Java <----- current index
2 .NET,Java
Which is not what I want. I mean the value should be updated via control myCustomControl. Here is what I want:
ID | Name
1 .NET,Java <----- current index
2
I can understand that, at the time moving back from index 1 to index 0, the value of List property is still a List with 2 items (".NET" and "Java") and so after the moving, this is updated to the cell at column Name in row 1. I'm finding how to reset that value of List property after it's updated to the cell at column Name in row 0 so that when the selection is at index 1, it's already empty. I'v tried changing the Parse event handler to the following but no good shake:
bind.Parse += (s,e) => {
List<string> data = (List<string>) e.Value;
if(data.Count == 0) e.Value = DBNull.Value;
else e.Value = string.Join(",",data.ToArray());//format as comma separated string
//I think at here, the value has been already updated to the datasource
//and I can perform the reset
myCustomControl.List.Clear();
};
But it seems to Clear before the value is updated to the datasource and so there is no value udpated to the datasource (instead of ".NET,Java", it's a DBNull.Value).
Then I have also tried this:
bind.BindingComplete += (s,e) => {
if(e.BindingCompleteContext == BindingCompleteContext.DataSourceUpdate)
myCustomControl.List.Clear();
};
I thought, it should check if the data is updated to the datasource, the List can be clear. I have also tried some flag to mark as true before clearing and reset it to false after clearing, use this flag to control the flowing in bind.Parse but it did nothing.
Do you have any idea to solve this problem? Your help would be highly appreciated! Thank you.
I've found the solution myself. In fact I can't reset the List anyway, this will update the underlying datasource when switching between rows in the grid. The key idea here is initially set DataSourceUpdateMode to DataSourceUpdateMode.Never, then whenever the List is about to change, turn the DataSourceUpdateMode to DataSourceUpdateMode.OnPropertyChanged. In the Parse event handler, after the parsing is done, reset DataSourceUpdateMode to DataSourceUpdateMode.Never. And that works greatly. The underlying datasource is updated only when user changing the control's value (List) by typing or selecting ,...
Here is all the code:
Binding bind = new Binding("List", myDataTable, "Name"){
ControlUpdateMode = ControlUpdateMode.Never, //Because my List property is readonly
DataSourceUpdateMode = DataSourceUpdateMode.Never//This will be turned on when preparing to change the List's value
};
//formating string data before updating to the datasource
bind.Parse += (s,e) => {
List<string> data = (List<string>) e.Value;
if(data.Count == 0) e.Value = DBNull.Value;
else e.Value = string.Join(",",data.ToArray());//format as comma separated string
//At here reset the DataSourceUpdateMode to Never
//We can also do this in BindingComplete event handler with BindingCompleteContext = BindingCompleteContext.DataSourceUpdate
myCustomControl.DataBindings[0].DataSourceUpdateMode = DataSourceUpdateMode.Never;
};
myCustomControl.DataBindings.Add(bind);
myCustomControl has a method to update/populate the new items for the List property called UpdateList(), we have to set DataSourceUpdateMode to OnPropetyChanged at the very beginning of the method like this:
public void UpdateList(){
if(DataBindings.Count > 0) DataBindings[0].DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
//The remaining code for populating/updating new items goes below
....
}
And that's all, very clean. Hope this will help someone who will encounter the same situation with me.Thank you!