How to populate classes needed for a web service - c#

I need to call a web service that accepts a class structure as input. The class itself is made up of regular field types plus one or more instances of a subclass. I was given example code that populates the class, but with static data. I need to populate it with dynamic data from user input at runtime and am not certain of the correct method to populate the subclass (PaymentProposalDetail) with 1 or more sets of data.
Here is the static data provided as an example:
PaymentApproval pa = new PaymentApproval()
{
VendorId = "14771",
Company = "333-TTTT",
PaymentApprovalNumber = "12345678",
SubmissioNDate = DateTime.Today,
ValueDate = DateTime.Today.AddMonths(1),
TotalAmount = 200000.00,
PaymentInstructionId = "ABCDEFGH",
Currency = "USD"
Comment = ""
};
pa.Details = new PaymentProposalDetail[] {
new PaymentProposalDetail()
{
InvoiceNumber = "1234567",
PayAmount = 454880.46,
InvoiceCurrency = "USD"
},
new PaymentProposalDetail()
{
InvoiceNumber = "9876543",
PayAmount = 543340.67,
InvoiceCurrency = "USD"
}
};
The class definitions:
public partial class PaymentApproval : object, System.Runtime.Serialization.IExtensibleDataObject
{
private string BankIdField;
private string CommentField;
private string CompanyField;
private string CurrencyField;
private PaymentProposalDetail[] DetailsField;
private string PaymentApprovalNumberField;
private string PaymentInstructionIdField;
private System.DateTime SubmissioNDateField;
private decimal TotalAmountField;
private System.DateTime ValueDateField;
private string VendorIdField;
}
public partial class PaymentProposalDetail : object, System.Runtime.Serialization.IExtensibleDataObject
{
private string InvoiceCurrencyField;
private string InvoiceNumberField;
private decimal PayAmountField;
}
I guess I need to build an array of PaymentProposalDetail objects within my loop that is getting the data from the UI. So for each set of data I do
PaymentApproval pa;
/* populate regular fields in pa */
foreach (data UIdata in UIdataStruct)
{
PaymentProposalDetail pd = new PaymentProposalDetail();
pd.InvoiceNumber = UIdata.invoiceNumber;
pd.PayAmount = UIdata.payAmount;
pd.InvoiceCurrenct = UIdata.invoiceCurrency;
}
But then how do I add pd to pa.Details?

Add each PaymentProposalDetail item to a collection and assign it to the PaymentApproval.
PaymentApproval pa;
/* populate regular fields in pa */
List<PaymentProposalDetail> details = new List<PaymentProposalDetail>();
foreach (data UIdata in UIdataStruct)
{
PaymentProposalDetail pd = new PaymentProposalDetail();
pd.InvoiceNumber = UIdata.invoiceNumber;
pd.PayAmount = UIdata.payAmount;
pd.InvoiceCurrenct = UIdata.invoiceCurrency;
details.Add(pd);
}
pa.Details = details.ToArray();

Related

Display all values stored in a column to a label?

I have a table called T_Score and the column called Team1, it has some stored values and I want these values to be added and displayed on a label.
This is the code which stores the values in the table:
private void Btn_LeaderB_Click(object sender, RoutedEventArgs e)
{
this.NavigationService.Navigate(new LeaderBoard());
SqlConnection conne = new SqlConnection(#"Data Source=LAPTOP-S2J1U9SJ\SQLEXPRESS;Initial Catalog=Unit4_IT;Integrated Security=True");
conne.Open();
string insertQuery = "insert into T_Score(Team1) " +
"values(#Team1)";
SqlCommand com = new SqlCommand(insertQuery, conne);
com.Parameters.AddWithValue("#Team1", txt_score4_tm1.Text);
com.ExecuteNonQuery();
conne.Close();
}
This code stores the value that needs to be added with the previous value.
Use the following query select sum(Team1) from T_Score; and instead of cmd.ExecuteNonQuery(); use cmd.ExecuteScalar(); and use the return value of the method ExecuteScalar
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.executescalar
Like this:
private void WriteToLabel()
{
using (SqlConnection conne = new SqlConnection(#"Data Source=LAPTOP-S2J1U9SJ\SQLEXPRESS;Initial Catalog=Unit4_IT;Integrated Security=True"))
{
conne.Open();
string selectQuery = "select sum(Team1) from T_Score;";
using (SqlCommand com = new SqlCommand(selectQuery, conne))
label1.Content = Convert.ToString(com.ExecuteScalar());
}
}
The line
label1.Content = Convert.ToString(com.ExecuteScalar());
Will require a Label called label1 for it to work.
It tries to write the result of Convert.ToString(com.ExecuteScalar()) into the Content property of said Label.
You can also use a TextBlock and than use the Text Property, so this line:
textBlock1.Text = Convert.ToString(com.ExecuteScalar());
Notice instead of calling conne.Close(); manually, I wrapped the SqlConnection conne into a using Statement, this should always be done with objects that inherit from IDisposable.
You can read about IDisposable and the using statement here:
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement
https://learn.microsoft.com/en-us/dotnet/api/system.idisposable
https://stackoverflow.com/a/61131/2598770
If you have the values already in a IEnumerable object eg. List<int> than you can just do the following
With a simple loop:
var myValues = new List<int>() { 1, 2, 3, 4, 5, 6 }; //<- list with temporary data
var sumOfValue = 0;
foreach (var myValue in myValues)
sumOfValue += myValue;
With LINQ:
var myValues = new List<int>() { 1, 2, 3, 4, 5, 6 }; //<- list with temporary data
var sumOfValue = myValues.Sum();
If you don't have an IEnumerable of int but instead a class than your code will look like this
public class MyValue
{
public int Value { get; set; }
//other properties
}
var myValues = new List<MyValue>()
{
new MyValue() { Value = 1 },
new MyValue() { Value = 2 },
new MyValue() { Value = 3 },
new MyValue() { Value = 4 },
new MyValue() { Value = 5 },
new MyValue() { Value = 6 },
}; //<- list with temporary data
With a simple loop:
var sumOfValue = 0;
foreach (var myValue in myValues)
sumOfValue += myValue.Value;
With LINQ:
var sumOfValue = myValues.Sum(x => x.Value);
In all those cases you will have to write sumOfValue into your Label or TextBlock like this
//Label
label1.Content = sumOfValue.ToString();
//or TextBlock
textBlock1.Text = sumOfValue.ToString();
To pass a value to a different page, all you would need to do is the following.
Search for your page in code behind eg. LeaderBoard.
It should look something like this
public partial class LeaderBoard : Page
{
//stuff...
}
Add a new Property to this class
public partial class LeaderBoard : Page
{
public int MyProperty { get; set; }
//stuff...
}
When you initialize the LeaderBoard eg. here
this.NavigationService.Navigate(new LeaderBoard());
Change the initialization to this:
this.NavigationService.Navigate(new LeaderBoard() { MyProperty = 7187, });
7187 is a random number this needs to be filled with what ever you need.
With this you have "transfered" data to LeaderBoard and the new property with its value can be accessed in LeaderBoard eg. there could be a method like this
public void Foo()
{
textBlock1.Text = Convert.ToString(this.MyProperty);
}
The class would than look like this:
public partial class LeaderBoard : Page
{
public int MyProperty { get; set; }
//stuff...
public void Foo()
{
textBlock1.Text = Convert.ToString(this.MyProperty);
}
}
If you need to change the MyProperty from a different place while the LeaderBoard is open keep the reference you have created.
To keep the reference change this line
this.NavigationService.Navigate(new LeaderBoard() { MyProperty = 7187, });
To this
_leaderBoard = new LeaderBoard() { MyProperty = 7187, };
this.NavigationService.Navigate(_leaderBoard);
And create a Field in the outer scope like this
private LeaderBoard _leaderBoard; //<- needs to be outside the method
private void Btn_LeaderB_Click(object sender, RoutedEventArgs e)
{
_leaderBoard = new LeaderBoard() { MyProperty = 7187, };
this.NavigationService.Navigate(_leaderBoard);
}
If Btn_LeaderB_Click gets called multiple times but you only want to create 1 LeaderBoard you can do this:
private void Btn_LeaderB_Click(object sender, RoutedEventArgs e)
{
if (_leaderBoard == null)
_leaderBoard = new LeaderBoard() { MyProperty = 7187, };
this.NavigationService.Navigate(_leaderBoard);
}
With that the _leaderBoard field will only get initialized once.
If you want to increase the value MyProperty everytime the Btn_LeaderB_Click method gets called you can further extend it to this:
private void Btn_LeaderB_Click(object sender, RoutedEventArgs e)
{
if (_leaderBoard == null)
_leaderBoard = new LeaderBoard() { MyProperty = 7187, };
else
_leaderBoard.MyProperty += int.Parse(txt_score4_tm1.Text);
this.NavigationService.Navigate(_leaderBoard);
}

How may I store an array of Xamarin.Forms.Point in SQLite?

How may I store an array of Xamarin.Forms.Point in an SQLite database?
I have the following method:
async void OnSaveButtonClicked(object sender, EventArgs e)
{
var temp = padView.Points;
var tempArray = padView.Points.ToArray();
var databaseItem = new DatabaseItem
{
Name = Name.Text,
Date = Date.Date.ToString(),
//PadViewPoints =
};
//var databaseItem = (DatabaseItem)BindingContext;
await App.Database.SaveItemAsync(databaseItem);
await Navigation.PopAsync();
}
Given there is a signature on the padView, when stepping through the aforementioned method, I see that tempArray has a value of Xamarin.Forms.Point[76].
I'd like to store this array of points in the database so that I may load it later when loading the item.
As Jason said, you could store it as a string, a practical implementation of that would be:
var temp = padView.Points;
var tempArray = padView.Points.ToArray();
var databaseItem = new DatabaseItem
{
Name = Name.Text,
Date = Date.Date.ToString(),
PadViewPointsString = JsonConvert.SerializeObject(tempArray)
};
And in the DatabaseItem class:
public class DatabaseItem
{
// All other properties
string padViewPointsString;
public string PadViewPointsString
{
get => padViewPointsString;
set
{
padViewPointsString = value;
PadViewPoints = JsonConvert.DeserializeObject<Xamarin.Forms.Point[]>(value);
}
}
public Xamarin.Forms.Point[] PadViewPoints { get; set; }
}
This will make the class deserialize the string every time you set it's value (e.g. when you get it from the serialized database).
Obs. This example is using Newtonsoft.Json library
Hope this helps! :)

c# data binding with a structure value

I need some help regarding a driver I`m building. I have a structure of some data in my static class. The data of this structure object has to be manipulated from outside my driver class. Into my class I have to prepare some textBoxes, which can be assigned and used from outside the class. Each structure value becomes one textBox. Now my problem is, I have to connect this dynamic changable structure values with the corresponding textBox. I have to use dataBinding, cause there will be a huge amount of data I have to use.
Pls check out the following code snippet for understanding:
public static class driver
{
#region " data preparation "
//structure definition
public struct _data
{
public string moduleName;
public string dynamicNumber1;
//...
}
//instance object of struct
private _data moduleData = new _data();
//get;set property
public _data pModuleData
{
get
{
return moduleData;
}
set
{
moduleData = value;
}
}
#endregion
//build data binding(s) for each single "moduleData.structureItem"
//???????????????????? moduleData_itemBinding_ModuleName
//???????????????????? moduleData_itemBinding_dynamicNumber1
//...
#region " form elements preparation for external assignments "
//instance of forms objects, data can be assigned and used outside of this public static class
public static System.Windows.Forms.TextBox textBox_ModuleName = new System.Windows.Forms.TextBox();
public static System.Windows.Forms.TextBox textBox_dynamicNumber1 = new System.Windows.Forms.TextBox();
#endregion
#region " class initialisation "
static driver()
{
// class initialisation part
textBox_ModuleName.DataBindings = moduleData_itemBinding_ModuleName; //assign databindings from above ???????????
textBox_ModuleName.DataBindings = moduleData_itemBinding_dynamicNumber1; //adding databindings from above ???????????
}
#endregion
}
Thanks for help!
KISS.
I think it is possible to make as follows. Define your class:
public class Driver
{
public Driver(TextBox moduleName, TextBox dynamicNumber)
{
textBox_ModuleName = moduleName;
textBox_DynamicNumber = dynamicNumber;
textBox_ModuleName.DataBindings.Add("Text", this, "ModuleName");
textBox_DynamicNumber.DataBindings.Add("Text", this, "DynamicNumber");
}
public string ModuleName { get; set; }
public string DynamicNumber { get; set; }
private TextBox textBox_ModuleName;
private TextBox textBox_DynamicNumber;
}
Then create textboxes on the form:
var textBox1 = new TextBox { Parent = this };
var textBox2 = new TextBox { Parent = this, Top = 30 };
Create instance of your class and pass to it these textboxes:
var driver = new Driver(textBox1, textBox2);
driver.ModuleName = "foo";
driver.DynamicNumber = "bar";
// data will be appear in the textboxes
It works.

How can I add array values into a combobox?

I have defined variables as string arrays. I also have a form called form4 and on this form I have 1 textbox and 1 combobox.
I have this code in a class:
public class food
{
public string[] name1 = new string [20];
public string[] name2 = new string [50];
public string[] name3 = new string [40] ;
public string[] name = new string [20];
public string[] type = new string [15];
//private const int total = 11;
public int[] calories_ref;
public int i, i2;
public Form4 form = new Form4();
public food(string[] Name1, string[] Name, string[] Type1, int[] kcal)
{
name1 = Name1;
name = Name;
type = Type1;
calories_ref = kcal;
}
public food()
{
}
private void settypes()
{
type[0] = "Bebidas não alcoólicas";
type[1] = "Bebidas Alcóolicas";
type[2] = "Fruta";
type[3] = "Hidratos de Carbono";
type[4] = "Peixe";
type[5] = "Carne";
type[6] = "Cereais";
type[7] = "Lacticínios";
type[8] = "Óleos/Gorduras";
type[9] = "Leguminosas";
type[10] = "Legumes";
for (int i = 0; i < type.Length; i++)
{
form.comboBox1.Items.Add(type[i]);
}
}
In the settypes() method I define the various types of food, more concretly the food wheel. How can I can use these values as items in the combobox that is in form4?
You can add an array of strings using method AddRange(array[]) from comboBox.
form.comboBox1.Items.AddRange(type);
Here is a void you can use if you want to go beyond just one array.
public void AddToCombo(Array array, ComboBox c)
{
foreach(var a in array)
{
c.Items.Add(a);
}
}
You shouldn't be storing a Form4 object in your food class. Your code as it is creates a brand new Form4 every time the food object is created. As shown in the line:
public Form4 form = new Form4();
This won't actually be shown on screen though as you do nothing else with the form except add items to the ComboBox, which also wouldn't show on the screen.
Even if you were to get it shown on the screen, you will still get an error similar to:
Form4.comboBox1 is inaccessible due to its protection level
This is due to the fact that the ComboBox is created internally with private access modifier. (See http://msdn.microsoft.com/en-us/library/ms173121.aspx for more details).
What you need to do is get your existing Form4 to ask the food object to populate it's ComboBox by passing the ComboBox to a method on the food object similar to this example (in your Form4 code not your food code):
private void Form4_Load(object sender, EventArgs e)
{
food f = new food(); //or however you wanted to create the object
f.AddTypesToComboBox(this.comboBox1);
}
The AddTypesToComboBox method would be defined like this in your food object:
public void AddTypesToComboBox(ComboBox box)
{
for (int i = 0; i < type.Length; i++)
{
box.Items.Add(type[i]);
}
}
Also, at the moment the function won't actually add anything to the ComboBox as your type array is not being filled with data. You need to call settypes(); in the food object's constructors like this:
public food(string[] Name1, string[] Name, string[] Type1, int[] kcal)
{
settypes();
name1 = Name1;
name = Name;
type = Type1;
calories_ref = kcal;
}
public food()
{
settypes();
}
You will need to remove public Form4 form = new Form4(); from your variable declaration section as well as removing the following from your settypes() method:
for (int i = 0; i < type.Length; i++)
{
form.comboBox1.Items.Add(type[i]);
}
Your settypes() should only fill the data into the array, and not try and add it to the ComboBox.
If you want the items to be in the ComboBox just set the array as its datasource. You don't need to loop through the array and add the items one by one. It's a lot less code than the other solutions here.
public void SetTypes()
{
comboBox1.DataSource = new[]{
"Bebidas não alcoólicas",
"Bebidas Alcóolicas",
"Fruta",
"Hidratos de Carbono",
"Peixe",
"Carne",
"Cereais",
"Lacticínios",
"Óleos/Gorduras",
"Leguminosas",
"Legumes"
};
}
Not at my computer at the moment, but this should do it:
foreach(var type in type[])
{
form.comboBox1.Items.Add(type);
}
As simple as this:
public void yourMethodName() {
yourComboBoxName.removeAllItems();
for (YourObjectName object: yourArrayListName) {
yourComboBoxName.addItem(object.getWhatYouWanaGet());
}
}
Your remove the actual item from the list than you add the item you want to add :)
If this is an Array in Class1:
public static string[] session = new string[] { "2023-2024", "2022-2023","2021-2022" };
In C# (Windows Forms):
comboBox1.Items.Clear();
comboBox1.Items.AddRange(Class1.session);

C# grid DataSource polymorphism

I have a grid, and I'm setting the DataSource to a List<IListItem>. What I want is to have the list bind to the underlying type, and disply those properties, rather than the properties defined in IListItem. So:
public interface IListItem
{
string Id;
string Name;
}
public class User : IListItem
{
string Id { get; set; };
string Name { get; set; };
string UserSpecificField { get; set; };
}
public class Location : IListItem
{
string Id { get; set; };
string Name { get; set; };
string LocationSpecificField { get; set; };
}
How do I bind to a grid so that if my List<IListItem> contains users I will see the user-specific field? Edit: Note that any given list I want to bind to the Datagrid will be comprised of a single underlying type.
Data-binding to lists follows the following strategy:
does the data-source implement IListSource? if so, goto 2 with the result of GetList()
does the data-source implement IList? if not, throw an error; list expected
does the data-source implement ITypedList? if so use this for metadata (exit)
does the data-source have a non-object indexer, public Foo this[int index] (for some Foo)? if so, use typeof(Foo) for metadata
is there anything in the list? if so, use the first item (list[0]) for metadata
no metadata available
List<IListItem> falls into "4" above, since it has a typed indexer of type IListItem - and so it will get the metadata via TypeDescriptor.GetProperties(typeof(IListItem)).
So now, you have three options:
write a TypeDescriptionProvider that returns the properties for IListItem - I'm not sure this is feasible since you can't possibly know what the concrete type is given just IListItem
use the correctly typed list (List<User> etc) - simply as a simple way of getting an IList with a non-object indexer
write an ITypedList wrapper (lots of work)
use something like ArrayList (i.e. no public non-object indexer) - very hacky!
My preference is for using the correct type of List<>... here's an AutoCast method that does this for you without having to know the types (with sample usage);
Note that this only works for homogeneous data (i.e. all the objects are the same), and it requires at least one object in the list to infer the type...
// infers the correct list type from the contents
static IList AutoCast(this IList list) {
if (list == null) throw new ArgumentNullException("list");
if (list.Count == 0) throw new InvalidOperationException(
"Cannot AutoCast an empty list");
Type type = list[0].GetType();
IList result = (IList) Activator.CreateInstance(typeof(List<>)
.MakeGenericType(type), list.Count);
foreach (object obj in list) result.Add(obj);
return result;
}
// usage
[STAThread]
static void Main() {
Application.EnableVisualStyles();
List<IListItem> data = new List<IListItem> {
new User { Id = "1", Name = "abc", UserSpecificField = "def"},
new User { Id = "2", Name = "ghi", UserSpecificField = "jkl"},
};
ShowData(data, "Before change - no UserSpecifiedField");
ShowData(data.AutoCast(), "After change - has UserSpecifiedField");
}
static void ShowData(object dataSource, string caption) {
Application.Run(new Form {
Text = caption,
Controls = {
new DataGridView {
Dock = DockStyle.Fill,
DataSource = dataSource,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false
}
}
});
}
As long as you know for sure that the members of the List<IListItem> are all going to be of the same derived type, then here's how to do it, with the "Works on my machine" seal of approval.
First, download BindingListView, which will let you bind generic lists to your DataGridViews.
For this example, I just made a simple form with a DataGridView and randomly either called code to load a list of Users or Locations in Form1_Load().
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Equin.ApplicationFramework;
namespace DGVTest
{
public interface IListItem
{
string Id { get; }
string Name { get; }
}
public class User : IListItem
{
public string UserSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public class Location : IListItem
{
public string LocationSpecificField { get; set; }
public string Id { get; set; }
public string Name { get; set; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void InitColumns(bool useUsers)
{
if (dataGridView1.ColumnCount > 0)
{
return;
}
DataGridViewCellStyle gridViewCellStyle = new DataGridViewCellStyle();
DataGridViewTextBoxColumn IDColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn NameColumn = new DataGridViewTextBoxColumn();
DataGridViewTextBoxColumn DerivedSpecificColumn = new DataGridViewTextBoxColumn();
IDColumn.DataPropertyName = "ID";
IDColumn.HeaderText = "ID";
IDColumn.Name = "IDColumn";
NameColumn.DataPropertyName = "Name";
NameColumn.HeaderText = "Name";
NameColumn.Name = "NameColumn";
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField";
DerivedSpecificColumn.HeaderText = "Derived Specific";
DerivedSpecificColumn.Name = "DerivedSpecificColumn";
dataGridView1.Columns.AddRange(
new DataGridViewColumn[]
{
IDColumn,
NameColumn,
DerivedSpecificColumn
});
gridViewCellStyle.SelectionBackColor = Color.LightGray;
gridViewCellStyle.SelectionForeColor = Color.Black;
dataGridView1.RowsDefaultCellStyle = gridViewCellStyle;
}
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = false;
Random rand = new Random();
bool useUsers = rand.Next(0, 2) == 0;
InitColumns(useUsers);
if(useUsers)
{
TestUsers();
}
else
{
TestLocations();
}
}
private void TestUsers()
{
List<IListItem> items =
new List<IListItem>
{
new User {Id = "1", Name = "User1", UserSpecificField = "Test User 1"},
new User {Id = "2", Name = "User2", UserSpecificField = "Test User 2"},
new User {Id = "3", Name = "User3", UserSpecificField = "Test User 3"},
new User {Id = "4", Name = "User4", UserSpecificField = "Test User 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
}
private void TestLocations()
{
List<IListItem> items =
new List<IListItem>
{
new Location {Id = "1", Name = "Location1", LocationSpecificField = "Test Location 1"},
new Location {Id = "2", Name = "Location2", LocationSpecificField = "Test Location 2"},
new Location {Id = "3", Name = "Location3", LocationSpecificField = "Test Location 3"},
new Location {Id = "4", Name = "Location4", LocationSpecificField = "Test Location 4"}
};
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
}
}
}
The important lines of code are these:
DerivedSpecificColumn.DataPropertyName = useUsers ? "UserSpecificField" : "LocationSpecificField"; // obviously need to bind to the derived field
public static void BindGenericList<T>(DataGridView gridView, List<T> list)
{
gridView.DataSource = new BindingListView<T>(list);
}
dataGridView1.AutoGenerateColumns = false; // Be specific about which columns to show
and the most important are these:
BindGenericList(dataGridView1, items.ConvertAll(item => (User)item));
BindGenericList(dataGridView1, items.ConvertAll(item => (Location)item));
If all items in the list are known to be of the certain derived type, just call ConvertAll to cast them to that type.
You'll need to use a Grid template column for this. Inside the template field you'll need to check what the type of the object is and then get the correct property - I recommend creating a method in your code-behind which takes care of this. Thus:
<asp:TemplateField HeaderText="PolymorphicField">
<ItemTemplate>
<%#GetUserSpecificProperty(Container.DataItem)%>
</ItemTemplate>
</asp:TemplateField>
In your code-behind:
protected string GetUserSpecificProperty(IListItem obj) {
if (obj is User) {
return ((User) obj).UserSpecificField
} else if (obj is Location) {
return ((Location obj).LocationSpecificField;
} else {
return "";
}
}
I tried projections, and I tried using Convert.ChangeType to get a list of the underlying type, but the DataGrid wouldn't display the fields. I finally settled on creating static methods in each type to return the headers, instance methods to return the display fields (as a list of string) and put them together into a DataTable, and then bind to that. Reasonably clean, and it maintains the separation I wanted between the data types and the display.
Here's the code I use to create the table:
DataTable GetConflictTable()
{
Type type = _conflictEnumerator.Current[0].GetType();
List<string> headers = null;
foreach (var mi in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
if (mi.Name == "GetHeaders")
{
headers = mi.Invoke(null, null) as List<string>;
break;
}
}
var table = new DataTable();
if (headers != null)
{
foreach (var h in headers)
{
table.Columns.Add(h);
}
foreach (var c in _conflictEnumerator.Current)
{
table.Rows.Add(c.GetFieldsForDisplay());
}
}
return table;
}
When you use autogeneratecolumns it doesnt automatically do this for you?
My suggestion would be to dynamically create the columns in the grid for the extra properties and create either a function in IListItem that gives a list of available columns - or use object inspection to identify the columns available for the type.
The GUI would then be much more generic, and you would not have as much UI control over the extra columns - but they would be dynamic.
Non-checked/compiled 'psuedo code';
public interface IListItem
{
IList<string> ExtraProperties;
... your old code.
}
public class User : IListItem
{
.. your old code
public IList<string> ExtraProperties { return new List { "UserSpecificField" } }
}
and in form loading
foreach(string columnName in firstListItem.ExtraProperties)
{
dataGridView.Columns.Add(new DataGridViewTextBoxColumn { DataPropertyName = columnName, HeaderText = columnName );
}
If you are willing to use a ListView based solution, the data-bindable version ObjectListView will let you do this. It reads the exposed properties of the DataSource and creates columns to show each property. You can combine it with BindingListView.
It also looks nicer than a grid :)

Categories