WPF Databinding problem - c#

I'm new to WPF and I have some difficulties when I'm trying to populate a ListView with a list of custom objects.
internal class ApplicationCode
{
public int Code { get; set; }
public IEnumerable<string> InstrumentCodes { get; set; }
}
I have a list of ApplicationCode which I set to ItemsSource to a ListView. I need to display the ApplicationCode.Code as a string and for the rest of the columns a check box which can be checked/unchecked depending if the column name is contained in the InstrumentCodes collection.
In order to set the check box I use a converter on databinding:
<DataTemplate x:Key="InstrumentCodeTemplate">
<CheckBox IsEnabled="False" IsChecked="{Binding Mode=OneTime, Converter={StaticResource InstrumentSelectionConverter}}" />
</DataTemplate>
The problem I have is because I can't know which is the current column at the time of cell data binding and I can't set the ConverterParameter.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ApplicationCode appCode = value as ApplicationCode;
return appCode != null && appCode.InstrumentCodes.Contains(parameter.ToString());
}
Small example:
Id | Code1 | Code3 | Code4
--------------------------------
123 | True | False | True
Data for row 1: ApplicationCode.InstrumentCodes {Code1, Code4}
There is a way to find out the column index or name? Or there is another way to solve this problem?

The column name should be nothing more then a visual; which means the needed data should all be residing in the underlying object model. Therefore each row of data is an object.
Perhaps a restructure of your code would suffice which would also remove the need for the converter...keep in mind this is an example to get the idea across and would need modified for actual use.
internal class ApplicationCode
{
private CodeService _codeService = new CodeService();
public int Code { get; set; }
public bool IsValidCode
{
get
{
return _codeService.DoesIntrumentCodeExist(Code.ToString());
}
}
}
internal class CodeService
{
private IEnumerable<string> _instrumentCodes;
public CodeService()
{
//instantiate this in another way perhaps via DI....
_instrumentCodes = new List<string>();
}
public bool DoesIntrumentCodeExist(String instrumentCode)
{
foreach (String code in _instrumentCodes)
{
if (code == instrumentCode)
return true;
}
return false;
}
}

The solution I got so far is to add the columns dynamically and to set the ConverterParameter on every column.
foreach (var instrument in instruments)
{
var column = new GridViewColumn
{
HeaderTemplate = GetColumnHeaderTemplate(instrument),
CellTemplate = GetColumnCellTemplate(instrument),
Header = instrument,
};
view.Columns.Add(column);
}
private static DataTemplate GetColumnCellTemplate(string instrument)
{
var binding = new Binding
{
ConverterParameter = instrument,
Converter = new InstrumentSelectionConverter(),
Mode = BindingMode.OneWay
};
var template = new DataTemplate();
template.VisualTree = new FrameworkElementFactory(typeof(CheckBox));
template.VisualTree.SetBinding(ToggleButton.IsCheckedProperty, binding);
return template;
}
I know this isn't the best solution and I would really appreciate if someone could show me a way to do this directly from .xaml.

Related

Binding List of Lists to DataGridView

For my current problem I want to create a DataGridView and use a list of objects as the datasource.
The problem I'm having is that the objects itself contains two lists, which are supposed to fill combobox columns. The DataGridView should contain three columns, each corresponding to my sample object below. The first column is a simple text column, while the other two are combo box columns.
Currently I'm receiving the error:
System.ArgumentException: The value DataGridViewComboBoxCell is invalid.
I've been looking for other solutions on SO, but can't seem to get it right.
public class SampleObject
{
public SampleObject(string name, IList<TimeSpan> startTimes, IList<Activity> completedActivities)
{
this.Name = name;
this.StartTimes = startTimes;
this.CompletedActivities = completedActivities;
}
public string Name { get; }
public IList<TimeSpan> StartTimes { get; }
public IList<Activity> CompletedActivities { get; }
}
Activity object:
public class Activity
{
public Activity(string activityName)
{
ActivityName = activityName;
}
public string ActivityName { get; }
public override string ToString()
{
return ActivityName;
}
}
And the code for adding the columns to my grid:
private void FillGrid()
{
sampleGrid.AutoGenerateColumns = false;
var columnName = new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(SampleObject.Name),
HeaderText = "Name",
Width = 160,
ReadOnly = true
};
sampleGrid.Columns.Add(columnName);
var columnStartTimes = new DataGridViewComboBoxColumn()
{
ValueType = typeof(TimeSpan),
HeaderText = "StartTimes",
Width = 120,
ReadOnly = false
};
sampleGrid.Columns.Add(columnStartTimes);
var columnCompletedActivities = new DataGridViewComboBoxColumn
{
ValueType = typeof(Activity),
HeaderText = "Completed activities",
Width = 120,
ReadOnly = false
};
sampleGrid.Columns.Add(columnCompletedActivities);
}
And populating the grid:
List<SampleObject> myObjects = GetSampleObjectsBasedOnValue(value);
sampleGrid.DataSource = myObjects;
FillComboBoxesInDGV(myObjects);
Method for filling the comboboxes:
private void FillComboBoxesInDGV(IList<SampleObject> sampleObjects)
{
for (int i = 0; i < sampleObjects.Count; i++)
{
DataGridViewRow row = sampleGrid.Rows[i];
var firstBox = row.Cells[1] as DataGridViewComboBoxCell;
firstBox.DataSource = sampleObjects[i].StartTimes;
var secondBox = row.Cells[2] as DataGridViewComboBoxCell;
secondBox.DataSource = sampleObjects[i].CompletedActivities;
}
}
I still not sure why you do not keep the original IList<string> CompletedActivities instead of introducing the Activity class? Using the list in this manner worked in my tests.
However, since you did use the class Activity, I would assumed it would work as expected. However, I ran into the same issue you described. After some hair pulling (and I don’t have much), I found some help from the SO post… DataGridViewComboBoxCell Binding - “value is not valid” … this was not obvious to me and I am guessing you also, thinking that overriding the Activity class’s ToString method would be enough. This is obviously not the case.
It appears that setting the DataGridViewComboBoxColumns’s ValueMember property to the name of the property in the Activity class should eliminate the error.
var columnCompletedActivities = new DataGridViewComboBoxColumn {
HeaderText = "Completed activities",
ValueMember = "ActivityName",
Width = 120,
};
sampleGrid.Columns.Add(columnCompletedActivities);
I hope this works for you.

How to let a wpf datagrid generate columns dynamically based on the content of a list?

I have a collection of objects and each object contains a property of the type collection. My goal is to generate dynamically the datagrid columns based on the collection content and generate also the columns for the remaining properties, which are base types. It is important that a bool is displayed as a CheckBox.
My problem is: The resulting cell content of the dynamically generated columns would be an object (Trait in my object structure), and I want one of this objects properties to be displayed (Trait.Value). When I change content of a cell, the object behind should update.
I thought of a DataTable, but when I add a row I need the column key and the value. When I set the value to a custom object, I couldn't see any possibility to display and edit a single property of the custom object.
Second approach would be using dynamic objects, like in the following article:
Auto-Generating DataGrid Columns From DynamicObjects
, but I see the same proplem like DataTable
Additional Information:
I am using mvvm (when it's necessary I would break this pattern)
the datagrid should be editable
My object structure:
public class Model
{
//ItemsSource
public ObservableCollection<Person> Persons { get; set; }
}
public class Person
{
public string Name { get; set; }
//Generate Treats.Count columns
public ObservableCollection<Treat> Treats { get; set; }
}
public class Treat
{
//column header name
public string Name { get; set; }
//value that should be displayed
public string Value { get; set; }
}
My ViewModel.cs with sample data:
public class ViewModel
{
public Model Model { get; set; }
public ViewModel()
{
#region Sample Data
Model = new Model()
{
Persons = new ObservableCollection<Person>()
{
new Person()
{
Name = "Peter",
Treats = new ObservableCollection<Treat>()
{
new Treat()
{
Name = "Look1",
Value = "Nice"
},
new Treat()
{
Name = "Look2",
Value = "Super Nice"
}
}
},
new Person()
{
Name = "Manuel",
Treats = new ObservableCollection<Treat>()
{
new Treat()
{
Name = "Look1",
Value = "Bad"
},
new Treat()
{
Name = "Look2",
Value = "Super Bad"
}
}
}
}
};
#endregion
}
}
Information to class Model.cs:
property Persons is the binding collection, which should be used as the ItemsSource
the columns of the datagrid should be generated based on the object Person. One column for the Name and n columns for the collection Treats.
The result based on my sample data is something like this:
Since, you have said you are okay to break MVVM pattern, please try the below approach.
OVERVIEW:
Create a IvalueConverter to convert your itemsource to list of expandoobejcts
In Code behind of the DataGrid (Loaded Event or SourceChanged event), add a code to generate columns manually
CODES:
Create Converter: PART 1 First we need to get the List of all possible columsn that might pop in (since we don't know the collections yet)
ObservableCollection<Person> inputlist = (ObservableCollection<Person>)value;
List<string> PossibleColumnList = new List<string>();
PossibleColumnList.Add(nameof(Person.Name)); //since we need name header first.
List<string> TempColumnList = new List<string>();
foreach (Person P in inputlist)
{
foreach(Treat T in P.Treats)
{
if (TempColumnList.Contains(T.Name) == false) TempColumnList.Add(T.Name);
}
}
TempColumnList.Sort();
PossibleColumnList.AddRange(TempColumnList); //This way we get Name first and rest of the columns in sorted out manner
Create Converter: PART 2. Now create an IDictionary Object with all available colum headers
IDictionary<string, object> ColumnHeaderDictionary = new Dictionary<string, object>();
foreach (string columnheader in PossibleColumnList)
{
if (ColumnHeaderDictionary.ContainsKey(columnheader) == false) ColumnHeaderDictionary.Add(columnheader, new object());
}
Create Converter: PART 3 Now iterate through all persons and create a IDictionary for each person model. Convert idictionary to expando object and store in final list
List<ExpandoObject> FinalList = new List<ExpandoObject>();
foreach (Person p in inputlist)
{
ExpandoObject tempExpando = new ExpandoObject();
IDictionary<string, object> TempDictionary = tempExpando as IDictionary<string, object>;
foreach (var kvp in ColumnHeaderDictionary)
{
TempDictionary.Add(kvp);
}
TempDictionary[nameof(Person.Name)] = p.Name;
foreach(Treat t in p.Treats)
{
TempDictionary[t.Name] = t.Value;
}
FinalList.Add(tempExpando);
}
return FinalList;
XAML CODE:
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid x:Name="grdMain" DataContext="{Binding}">
<DataGrid x:Name="dgMain" ItemsSource="{Binding ElementName=grdMain,Path=DataContext.Model.Persons,Converter={StaticResource NewConverter}}" Loaded="dgMain_Loaded" />
</Grid>
CODE BEHIND: TO MANUALLY CREATE COLUMNS
private void dgMain_Loaded(object sender, RoutedEventArgs e)
{
DataGrid workinggrid = sender as DataGrid;
ExpandoObject SingleExpando = (workinggrid.ItemsSource as List<ExpandoObject>).FirstOrDefault();
if (workinggrid == null) workinggrid = new DataGrid();
List<string> ColumHeaders = (SingleExpando as IDictionary<string, object>).ToList().Select(p => p.Key).ToList();
foreach (string ColumnName in ColumHeaders)
{
var newcolumn = new DataGridTextColumn() { Header = ColumnName, Binding = new Binding(ColumnName) };
workinggrid.Columns.Add(newcolumn);
}
}
FINAL OUTPUT:

I'm retrieving a value for a combobox with custom class items but I can't make it show the item

I wanted to have items and hidden values which I could call later so I used this Article to create my custom items.
But now that I'm calling one value I cannot make it show the proper item. The combobox stays null.
if (reader.HasRows)
{
reader.Read();
namebox.Text = reader["c_Name"].ToString();
lastbox.Text = reader["c_LastName"].ToString();
genderbox.SelectedItem = reader["c_gender"].ToString();
}
Here is what I add to my combobox and what I want to show accoring to what value I get from the reader
private void editcust_Load(object sender, EventArgs e)
{
genderbox.Items.Add(new ComboBoxItem("male", "1"));
genderbox.Items.Add(new ComboBoxItem("female", "0"));
}
Please let me know if I need to add more code or provide more information.
I'm a junior developer so please excuse my terrible mistakes and bad formulation.
First, override Equals and GetHashCode methods in your class:
public class ComboBoxItem()
{
string displayValue;
string hiddenValue;
//Constructor
public ComboBoxItem (string d, string h)
{
displayValue = d;
hiddenValue = h;
}
//Accessor
public string HiddenValue
{
get
{
return hiddenValue;
}
}
public override bool Equals(object obj)
{
ComboBoxItem item = obj as ComboBoxItem;
if (item == null)
{
return false;
}
return item.hiddenValue == this.hiddenValue;
}
public override int GetHashCode()
{
if (this.hiddenValue == null)
{
return 0;
}
return this.hiddenValue.GetHashCode();
}
//Override ToString method
public override string ToString()
{
return displayValue;
}
}
Then assign a new Item to the SelectedItem property:
genderbox.SelectedItem = new ComboBoxItem(string.Empty, reader["c_gender"].ToString());
When you assign a value to the SelectedItem property of ComboBox, it looks in it's items collection and tries to find an item that is equal to the assigned value. If it find an item equal to the value, that item gets selected. In the process, comparison is done by the Equals method of each item.
By overriding the method, you tell ComboBox to compare items using the "hiddenValue" field, so when you assign a new item with ite's hiddenValue set, combobox can find it in it's items collection. If you don't do that, equality comparison will be done using object references instead.
Use the DisplayMember & ValueMember properties of the ComboBox-Class and assign a DataSource.
ie. Your Data Class:
private class yourDataClass
{
public string DisplayMemberProperty { get; set; }
public int IDMember { get; set; }
}
Assign the datasource with values to the combobox
var dataSource = new ArrayList();
dataSource.Add(new yourDataClass() { DisplayMemberProperty = "Hello", IDMember = 1 });
dataSource.Add(new yourDataClass() { DisplayMemberProperty = "Hello2", IDMember = 2 });
dataSource.Add(new yourDataClass() { DisplayMemberProperty = "Hello3", IDMember = 2 });
this.comboBox1.DataSource = dataSource;
this.comboBox1.DisplayMember = "DisplayMemberProperty";
this.comboBox1.ValueMember = "IDMember";
Retreive the selected value...
var value = this.comboBox1.SelectedValue;
Agreed the question is unclear but if you mean that this call fails:
genderbox.SelectedItem = reader["c_gender"].ToString();
It's probably because that you need to use the same kind of value that you originally populated the list with.
i.e. if you populated it with instances of class x you need to set selectedItem to an instance of class x.

Windows Phone 7 ListPicker InvalidCastException

I have a problem building a simple crud form in WP7. I have spent a lot of time to display an Enum into a listpicker an now I see InvalidCastException when trying to bind to the (IsolatedStorage) object.
public class Bath {
public string Colour { get; set; }
public WaterType WaterType { get; set; }
}
public enum WaterType {
Hot,
Cold
}
The enum is bound to a ListPicker, but as there is not enum.GetValues() in WP7 this is not a simple task.
I have a simple type class...
public class TypeList
{
public string Name { get; set; }
}
And in my viewmodel, I have ObservableCollection and mock the values from the enum...
private ObservableCollection<TypeList> _WaterTypeList;
public ObservableCollection<TypeList> WaterTypeList
{
get { return _WaterTypeList; }
set
{
_WaterTypeList= value;
NotifyPropertyChanged("WaterTypeList");
}
}
public void LoadCollectionsFromDatabase()
{
ObservableCollection<TypeList> wTypeList = new ObservableCollection<WaterTypeList>();
wTypeList.Add(new TypeList{ Name = WaterType.Hot.ToString() });
wTypeList.Add(new TypeList{ Name = WaterType.Income.ToString() });
WaterTypeList = new ObservableCollection<TypeList>(wTypeList);
}
Finally, my xaml contains the listbox...
<toolkit:ListPicker
x:Name="BathTypeListPicker"
ItemsSource="{Binding WaterTypeList}"
DisplayMemberPath="Name">
</toolkit:ListPicker>
Im not sure if the above is best practise and indeed if the above is part of the problem but the above does give me a populated ListPicker.
Finally, when the form is submitted the cast causes a InvalidCastException.
private void SaveAppBarButton_Click(object sender, EventArgs e)
{
var xyz = WaterTypeList.SelectedItem; // type AppName.Model.typeList
Bath b = new Bath
{
Colour = ColourTextBox.Text ?? "Black",
WaterType = (WaterType)WaterTypeListPicker.SelectedItem
};
App.ViewModel.EditBath(b);
NavigationService.Navigate(new Uri("/Somewhere.xaml", UriKind.Relative));
}
}
Has anyone faced a simlar problem and can offer advice. I see that my opions are to concentrate on casting something meaningful from the ListPicker or should I rethink the way that the ListPicker is populated?
As far as I can see, WaterTypeList is an ObservableCollection that is a Type of and an observable collection doesn't have a SelectedItem property.
Your Bath class has a WaterType that accepts WaterType property and you are trying to cast a WaterTypeListPicker.SelectedItem to it.. so I'm assuming your WatertypeListPicker is your ListBox?
If it is, then you are doing it wrong because your ListBox's itemssource is bound to a class and you are trying to add a to your WaterType Property.
What I would do is say,
Bath b = new Bath
{
Colour = ColourTextBox.Text ?? "Black",
WaterType = WaterTypeListPicker.SelectedItem
};
Either change my property of Bath's WaterType to a TypeList so the above code would work. But I won't recommend doing another class to wrap the enum just to show it to the listbox.
What I would do is create an EnumHelper
public static class EnumExtensions
{
public static T[] GetEnumValues<T>()
{
var type = typeof(T);
if (!type.IsEnum)
throw new ArgumentException("Type '" + type.Name + "' is not an enum");
return (
from field in type.GetFields(BindingFlags.Public | BindingFlags.Static)
where field.IsLiteral
select (T)field.GetValue(null)
).ToArray();
}
public static string[] GetEnumStrings<T>()
{
var type = typeof(T);
if (!type.IsEnum)
throw new ArgumentException("Type '" + type.Name + "' is not an enum");
return (
from field in type.GetFields(BindingFlags.Public | BindingFlags.Static)
where field.IsLiteral
select field.Name
).ToArray();
}
}
And Bind it to a Collection
My ViewModel
public IEnumerable<string> Priority
{
get { return EnumExtensions.GetEnumValues<Priority>().Select(priority => priority.ToString()); }
public string SelectedPriority
{
get { return Model.Priority; }
set { Model.Priority = value; }
}
Like that.
My XAML.
<telerikInput:RadListPicker SelectedItem="{Binding SelectedPriority, Mode=TwoWay}" ItemsSource="{Binding Priority}" Grid.Column="1" Grid.Row="4"/>
The WaterTypeListPicker.SelectedItem is an object of type TypeList so it can't be cast to an object of type WaterType.
In order to convert back to a WaterType, you could replace your cast:
WaterType = (WaterType)WaterTypeListPicker.SelectedItem
with:
WaterType = (WaterType)Enum.Parse(
typeof(WaterType),
((TypeList)WaterTypeListPicker.SelectedItem).Name,
false)

C# WPF Binding a text file to datagrid

How can I bind a text file to a datagrid with C# WPF? The idea is to have a row in the text file to show as a row in datagrid.
I don't think you can bind text directly to a datagrid
What you can do however is bind an objet to a datagrid
create an objet representing your text file.
-- content --
text1, param1, param2
text2, param1, param2
class OneLine{
string text {get;set;}
string param { get;set; }
...
}
You can then bind those objects to the datagrid with a BindingList, which is mostly a List. The magic lies in the Properties of the object. The BindingList will try to get each property of the object and display them in the grid.
BindingList<OneLine> myList = new BindingList<OneLine>();
myList.Add(oneObject);
DataGrid myGrid = new DataGrid();
myGrid.DataSource = myList;
In my project I use the following approach
Create class that represent row in text file for example
public class cls_syslog_record
{
public DateTime? f1 {get;set;}
public string f2 {get;set;}
public string f3 {get;set;}
public string f4 {get;set;}
}
Create IEnumerable that used as source for DataGrid
public IEnumerable<cls_syslog_record> get_line_seq_text()
{
cls_mvs_syslog_parser parser = new cls_mvs_syslog_parser();
foreach (string record_line in File.ReadLines(this.filename))
{
cls_syslog_record text_record = parser.parse_syslog_text(record_line);
if (text_record == null)
{
continue;
}
yield return text_record;
}
}
set my IEnumerable object as source
static private DataGrid make_text_viewer(string p_filename)
{
logger.Debug("start");
DataGrid table_viewer;
cls_file_line_seq fl_seq = new cls_file_line_seq(p_filename);
table_viewer = new DataGrid();
table_viewer.CanUserAddRows = false;
table_viewer.CanUserDeleteRows = false;
table_viewer.Columns.Add(create_column("Date Time", "timestamp"));
table_viewer.Columns.Add(create_column("LPAR Name", "lpar_name"));
table_viewer.Columns.Add(create_column("JOB ID", "job_id"));
table_viewer.Columns.Add(create_column("Message", "message"));
table_viewer.HeadersVisibility = DataGridHeadersVisibility.All;
table_viewer.ItemsSource = fl_seq.get_line_seq_text();
return table_viewer;
}
Then setup binding
static private DataGridColumn create_column(string header, string p_property_name)
{
DataGridTextColumn column = new DataGridTextColumn();
column.Header = header;
column.Binding = new Binding(p_property_name);
return column;
}

Categories

Resources