Dynamic combobox list databinding in C# WinForms - c#

Have read a lot this resource and really like it, but now i met problem which i can't neither resolve by myself nor find similar solution.
I'm using C# winforms and linqtosql.
In my userforms I use additional view-class to bind list for comboboxes to let users be able to get and use a name-list of objects while being forbidden to get a whole object itself. (This is not question whether it is a good practice, no matter.)
For example(this is not real code, just for look):
ORM classes:
public class Contract
{
public string ID { get; set; }
public string Name { get; set; }
public Contractor Contractor { get; set; }
public string ContractorID { get; set; }
}
public class Contractor
{
public string ID { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
This is additional view-class which is mapping for sqlserver view Contractor_List (SELECT c.ID, c.Name FROM Contractors c)
public class Contractor_List
{
public string ID { get; set; }
public string Name { get; set; }
}
UserForm:
public class ContractForm : Form
{
void Init()
{
TextBox nameBox = new TextBox();
ComboBox contractorBox = new ComboBox();
BindingSource contractSource = new BindingSource();
contractSource.DataSource = typeof (Contract);
nameBox.DataBindings.Add("Text", contractSource, "Name", false, DataSourceUpdateMode.OnValidation);
contractorBox.DataBindings.Add("SelectedValue", contractSource, "ContractorID", false, DataSourceUpdateMode.OnValidation);
BindingSource contractorListSource = new BindingSource();
contractorListSource.DataSource = typeof (Contractor_List);
contractorBox.DisplayMember = "Name";
contractorBox.ValueMember = "ID";
}
}
OK.
My idea is to load contractorBox.DataSource (it’s binding source) when contractorBox.SelectedValue is set.
I found that SelectedValue is not overridable, so I decided to inherit combobox and to create in it a new property called “ID” and do following stuff instead
In form:
contractorBox.DataBindings.Add("ID", contractSource, "ContractorID");
In Control (this is real code):
object _id;
bool _listInitialized;
public object ID
{
get
{
return _id;
}
set
{
if (!_listInitialized)
{
var bindingSource = DataSource as BindingSource;
if (bindingSource != null)
{
var t = (bindingSource.DataSource as Type);
var rst = … //Getting List
if (rst!=null)
{
bindingSource.DataSource = rst;
_listInitialized = true;
SelectedValueChanged += delegate {
if (SelectedValue != ID)
{
ID = SelectedValue;
}
};
}
}
}
else
{
_id = value;
if (SelectedValue != ID)
{
SelectedValue = value;
}
}
}
}
So, this code works fine. I can load form, Contract object and list of contractors and get right contractor name in combobox.
But. I have problem with backing Binding of “ID” property. When contractor in combobox is changed Contract object doesn’t update (neither ContractorID, no Contractor Itself) while ID, SelectedValue and SelectedItem of combobox change properly.
Why? What have I do to make this working.

Hah. So Simple Solution.
public new object SelectedValue
{
get
{
return base.SelectedValue;
}
set
{
if (!DesignMode)
{
if (!_listInitialized)
{
var bindingSource = DataSource as BindingSource;
if (bindingSource != null)
{
var t = (bindingSource.DataSource as Type);
var rst = ...///how you get your type list
if (rst != null)
{
bindingSource.DataSource = rst;
_listInitialized = true;
}
}
}
else
{
base.SelectedValue = value;
}
}
}
}
May be will be helpful for someone.

Related

Sharing Object between Views & ViewModels in MVVM

I'm new to WPF + MVVM and have been having trouble getting around viewmodels.
I have a object called FSystem which contains a alot of lists which are populated from a XML.
public class FSystem : ObservableObject
{
public List<FUser> _userList;
public List<FZone> _zoneList;
public List<FSource> _sourceList;
public string _projectName { get; set; }
private string _projectVersion { get; set; }
private string _processorIp { get; set; }
private bool _isMultiLingualModeOn { get; set; }
private int _systemIncludeLighting { get; set; }
private int _systemIncludeWindowsTreatments { get; set; }
private int _systemIncludeSip { get; set; }
private int _systemIncludeCamaras { get; set; }
public FSystem()
{
UserList = new List<FUser>();
}
}
This is the XMLParser which is called when the user loads the XML to the application.
public static class XMLParsers
{
public static FSystem ParseByXDocument(string xmlPath)
{
var fSystem = new FSystem();
XDocument doc = XDocument.Load(xmlPath);
XElement fSystemElement = doc.Element("FSystem");
if (fSystemElement != null)
{
fSystem.ProjectName = fSystemElement.Element("ProjectName").Value;
fSystem.ProjectVersion = fSystemElement.Element("ProjectVersion").Value;
fSystem.ProcessorIp = fSystemElement.Element("ProcessorIP").Value;
fSystem.ProcessorFilePath = fSystemElement.Element("ProcessorFilePath").Value;
fSystem.SystemIncludeLighting = Convert.ToInt16(fSystemElement.Element("SystemIncludeLighting").Value);
fSystem.SystemIncludeSip = Convert.ToInt16(fSystemElement.Element("SystemIncludeLighting").Value);
fSystem.SystemIncludeCamaras = Convert.ToInt16(fSystemElement.Element("SystemIncludeCameras").Value);
}
fSystem.UserList = (from user in doc.Descendants("FUser")
select new FUser()
{
Id = user.Element("Id").Value,
Name = user.Element("Name").Value,
Icon = user.Element("IconColour").Value,
Pin = user.Element("UserPin").Value,
IsPinEnabled = Convert.ToBoolean(Convert.ToInt16(user.Element("UserPinEnabled").Value)),
ListIndex = user.Element("ListIndex").Value
}).ToList();
return fSystem;
}
}
And this is the MainViewModel below is what contains the Commands which Load the XML and the property FSystem I wish to use in other view models.
public class MainViewModel : ViewModel
{
private Fystem fSystem;
public FSystem FSystem
{
get { return fSystem; }
private set
{
fSystem = value;
NotifyPropertyChanged("FSystem");
}
}
public MainViewModel()
{
InitiateState();
WireCommands();
}
private void InitiateState()
{
FSystem = new FSystem();
}
private void WireCommands()
{
XDocumentLoadCommand = new RelayCommand(XDocumentLoad) {IsEnabled = true};
ClearDataCommand = new RelayCommand(ClearData) {IsEnabled = true};
}
public RelayCommand XDocumentLoadCommand { get; private set; }
private void XDocumentLoad()
{
var openDlg = new OpenFileDialog
{
Title = "Open .FAS",
DefaultExt = ".fas",
Filter = "F System Files (*.fas)|*.fas",
Multiselect = false
};
bool? result = openDlg.ShowDialog() == DialogResult.OK;
if (result != true) return;
FSystem = XMLParsers.ParseByXDocument(openDlg.FileName);
}
The application basically lets the user change the different objects (FUser,FZone,FSource, ect). The idea I had was the user would load the XML then be able to edit the different list objects on different views.
What would the correct way be to go about this in MVVM?
I plan to (hopefully) get the User, Zone and Source views to display Datagrids which are populated with their respective data from the Model.
Create you specific view models, and use dependency injection to pass the relevant data into them (this list or that list).
This way, the view models don't need to know about other stuff, and you can easily mock it for testing and for dummy data to see on the designer.
Copy paste into Linqpad for the simplest example. Both mock viewmodels take a dependency (i in our case). You can just pass your lists:
void Main()
{
int someInt = 5;
int anotherInt = 7;
VM vm1 = new VM(someInt);
VM vm2 = new VM(anotherInt);
vm1.RevealI();
vm2.RevealI();
}
public class VM{
private int _i;
public VM(int i)
{
_i = i;
}
public void RevealI() { Console.WriteLine("value of i is: " + _i); }
}
Othen than that, here's more items:
MSDN
Code Project
stack overflow

Initializing an object property to the value of another object property in DataGridView

I'm attempting to bind two objects (List LedgerEntries and List BuyerSellers) to a single DataGridView. LedgerEntry contains a property for Buyer_Seller, and I would like the end-user to select a Buyer_Seller from a combobox (populated by the BuyerSellers generic collection) in the DataGridView and the LedgerEntries string BuyerSeller property be set to the Buyer_Seller string Name property.
At the moment I'm only using one BindingSource and I'm not defining my own columns; they are auto-generated based on the object being bound to the DGV. Where I'm a little lost is how to ensure the property in one object is initialized to the value of the combobox that's populated by another object. Thanks in advance for any help.
Found what I was looking for here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/62ddde6c-ed96-4696-a5d4-ef52e32ccbf7/binding-of-datagridviewcomboboxcolumn-when-using-object-binding
public partial class Form1 : Form
{
List<LedgerEntry> ledgerEntries = new List<LedgerEntry>();
List<Address> addresses = new List<Address>();
BindingSource entrySource = new BindingSource();
BindingSource adSource = new BindingSource();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
entrySource.DataSource = ledgerEntries;
adSource.DataSource = addresses;
DataGridViewComboBoxColumn adr = new DataGridViewComboBoxColumn();
adr.DataPropertyName = "Address";
adr.DataSource = adSource;
adr.DisplayMember = "OrganizationName";
adr.HeaderText = "Organization";
adr.ValueMember = "Ref";
ledger.Columns.Add(adr);
ledger.DataSource = entrySource;
addresses.Add(new Address("Test1", "1234", 5678));
addresses.Add(new Address("Test2", "2345", 9876));
}
private void button1_Click(object sender, EventArgs e)
{
foreach (LedgerEntry le in ledgerEntries)
MessageBox.Show(le.Address.OrganizationName + " // " + le.Description);
}
}
public class LedgerEntry
{
public string Description { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string OrganizationName { get; set; }
public string StreetAddress { get; set; }
public int ZipCode { get; set; }
public Address(string orgname, string addr, int zip)
{
OrganizationName = orgname;
StreetAddress = addr;
ZipCode = zip;
}
public Address Ref
{
get { return this; }
set { Ref = value; }
}
public override string ToString()
{
return this.OrganizationName;
}
}

DataGridView and saving cell values to a complex underlying DataSource

I am using EF to return a List of Processes, with one to many flags. Flags are unique, they may increase or decrease depending on requirements. The data structure roughly translates to:
public enum FlagTypes
{
OnlyOnWeekends,
OnlyOnHolidays
}
public class Process
{
public DateTime Date { get; set; }
public String Description { get; set; }
public Dictionary<FlagTypes, Flag> Flags { get; set; }
}
public class Flag
{
public FlagTypes Type { get; set; }
public bool Enabled { get; set; }
}
I would like to display this in a DataGridView like so:
Date | Description | OnlyOnWeekends | OnlyOnHolidays [|... more flags as needed]
.. while also making it editable.
I was able to work around the limits of DataGridView to display the table using a custom column and cell
public class EnumerationColumn : DataGridViewColumn
{
public FlagTypes EnumerationType { get; set; }
public EnumerationColumn(FlagTypes enumerationType)
: base(new EnumerationCell())
{
EnumerationType = enumerationType;
}
public override DataGridViewCell CellTemplate
{
get
{
return base.CellTemplate;
}
set
{
// Ensure that the cell used for the template is a EnumerationCell.
if (value != null &&
!value.GetType().IsAssignableFrom(typeof(EnumerationCell)))
{
throw new InvalidCastException("Must be a EnumerationCell");
}
base.CellTemplate = value;
}
}
public class EnumerationCell : DataGridViewCheckBoxCell
{
private EnumerationColumn Parent
{
get
{
var parent = base.OwningColumn as EnumerationColumn;
if(parent == null)
{
throw new NullReferenceException("EnumerationCell must belong to a EnumerationColumn");
}
return parent;
}
}
private Dictionary<FlagTypes, Flag> GetFlags(int rowIndex)
{
var flags = base.GetValue(rowIndex) as Dictionary<FlagTypes, Flag>;
return flags ?? new Dictionary<FlagTypes, Flag>();
}
protected override object GetValue(int rowIndex)
{
var flags = GetFlags(rowIndex);
if (flags.ContainsKey(Parent.EnumerationType))
{
return flags[Parent.EnumerationType].Enabled;
}
return false;
}
}
}
And creating the columns
grid.AutoGenerateColumns = false;
grid.DataSource = processes; // List<Process>
var dateCol = new CalendarWidgetColumn();
dateCol.DataPropertyName = "Date";
dateCol.HeaderText = "Date";
var descCol = new DataGridViewTextBoxColumn();
descCol.DataPropertyName = "Description";
descCol.HeaderText = "Description";
grid.Columns.Add(dateCol);
grid.Columns.Add(descCol);
foreach(string name in Enum.GetNames(typeof(FlagTypes)))
{
FlagTypes flag;
if(FlagTypes.TryParse(name, out flag))
{
var enumCol = new EnumerationColumn(flag);
enumCol.DataPropertyName = "Flags";
enumCol.HeaderText = String.Format("{0}?", name);
grid.Columns.Add(enumCol);
}
}
I cannot figure out how to intercept the call to save to the DataSource, so it is throwing an Exception trying to set a bool (the checkbox value) to a Dictionary (the Flags DataProperty). I have looked at the CellValuePushed event, but that fires after the fact. Any ideas?
Or perhaps an easier way to approach this all together?
(Eventually I want to wrap the processes list with a BindingList so I can also create new rows directly from the DataGridView)
Solution (as suggested by #Erez Robinson below)
Step 1: Modify DataStructure to allow easy access to underlying dictionary
public enum FlagType
{
OnlyOnWeekends,
OnlyOnHolidays
}
public class Process
{
public DateTime Date { get; set; }
public String Description { get; set; }
public Dictionary<FlagType, Flag> Flags { get; set; }
public bool this[FlagType flagType]
{
get
{
if(!Flags.ContainsKey(flagType))
{
Flags.Add(flagType, new Flag(flagType, false));
}
return Flags[flagType].Enabled;
}
set
{
Flags[flagType].Enabled = value;
}
}
}
public class Flag
{
public FlagType Type { get; set; }
public bool Enabled { get; set; }
public Flag(FlagType flagType, bool enabled)
{
Type = flagType;
Enabled = enabled;
}
}
Step 2: Create a container that derives from ITypedList
class ProcessCollection : List<Process>, ITypedList
{
protected IProcessViewBuilder _viewBuilder;
public ProcessCollection(IProcessViewBuilder viewBuilder)
{
_viewBuilder = viewBuilder;
}
#region ITypedList Members
protected PropertyDescriptorCollection _props;
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
if (_props == null)
{
_props = _viewBuilder.GetView();
}
return _props;
}
public string GetListName(PropertyDescriptor[] listAccessors)
{
return ""; // was used by 1.1 datagrid
}
#endregion
}
Step 3: Move dynamic property creation to a ViewBuilder
public interface IProcessViewBuilder
{
PropertyDescriptorCollection GetView();
}
public class ProcessFlagView : IProcessViewBuilder
{
public PropertyDescriptorCollection GetView()
{
List<PropertyDescriptor> props = new List<PropertyDescriptor>();
props.Add(new DynamicPropertyDescriptor(
typeof(Process),
"Date",
typeof(DateTime),
delegate(object p)
{
return ((Process)p).Date;
},
delegate(object p, object newPropVal)
{
((Process)p).Date = (DateTime)newPropVal;
}
));
props.Add(new DynamicPropertyDescriptor(
typeof(Process),
"Description",
typeof(string),
delegate(object p)
{
return ((Process)p).Description;
},
delegate(object p, object newPropVal)
{
((Process) p).Description = (string) newPropVal;
}
));
foreach (string name in Enum.GetNames(typeof(FlagType)))
{
FlagType flag;
if (FlagType.TryParse(name, out flag))
{
props.Add(new DynamicPropertyDescriptor(
typeof (Process),
name,
typeof (bool),
delegate(object p)
{
return ((Process) p)[flag];
},
delegate(object p, object newPropVal)
{
((Process) p)[flag] = (bool) newPropVal;
}
));
}
}
PropertyDescriptor[] propArray = new PropertyDescriptor[props.Count];
props.CopyTo(propArray);
return new PropertyDescriptorCollection(propArray);
}
}
Step 4: Bind the collection to the DataGridView
ProcessCollection processes = new ProcessCollection(new ProcessFlagView());
// Add some dummy data ...
processes.Add( ... );
// If you want a custom DataGridViewColumn, bind it before you bind the DataSource
var dateCol = new CalendarColumn();
dateCol.DataPropertyName = "Date";
dateCol.HeaderText = "Date";
grid.Columns.Add(dateCol);
grid.DataSource = processes;
You can derive from DataGridView and shadow the DataSource property with the new keyword.
But I think that you need to change your concept.
I would not touch the datagridview.
Exposes these Flags as seperate Properties. Add these classes to a binding list.
You can dynamicaly create properties using reflection.
Please read this article, it will point you in the right direction: ITypedList

do not work "set" in some of property in design time

why in desin time do not work set "dataGrid" Property in my code.but property font,with working correct.
I used of this componet on the a Form.(i test it with design time debuging)
enter code here
namespace Example
{
public partial class Component1 : System.Windows.Forms.DataGridView
{
public Component1()
{
}
private DataGridViewColumn _gridcolumn;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[TypeConverter(typeof ( ExpandableObjectConverter))]
public DataGridViewColumn gridcolumn
{
get
{
if (_gridcolumn == null)
_gridcolumn = new DataGridViewColumn();
return _gridcolumn;
}
set //Do not Work Set in designTime
{
_gridcolumn = value;
}
}
private System.Drawing.Font _MyFont;
public System.Drawing.Font MyFont
{
get
{
if (_MyFont == null)
_MyFont = new System.Drawing.Font("tahoma", 8);
return _MyFont;
}
set
{
_MyFont = value; //Work correctly in design time
}
}
int _with;
public int withcustom
{
get
{
return _with;
}
set
{
_with = value; //Work correctly in design time
}
}
}
}
Short Version
Either remove DesignerSerializationVisibility(DesignerSerializationVisibility.Content) from your property, or change it to DesignerSerializationVisibility(DesignerSerializationVisibility.Visible).
Long Version
Applying DesignerSerializationVisibility(DesignerSerializationVisibility.Content) to your property means that the WinForms designer will not generate code that sets the value of the property, but rather code that sets the value of the properties on the value of that property.
For example, say I have these two types:
public class MyControl : Control
{
public class MyControlProperties
{
public string Prop1 { get; set; }
public int Prop2 { get; set; }
}
private MyControlProperties props;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public MyControlProperties Properties
{
get
{
if(props == null) props = new MyControlProperties();
return props;
}
}
}
When the designer generates the code for MyControl on a form, it will look something like this:
myControl.Properties.Prop1 = "foo";
myControl.Properties.Prop2 = 10;
So, instead of setting the value of the Properties property (which, in this code, is read-only; though it doesn't have to be, properties like this usually are), it's setting the values of the properties on the value of that property.
This is good example:
public partial class SCon : UserControl
{
public SCon()
{
InitializeComponent();
if (Persoanas == null)
{
Persoanas = new List<Persoana>();
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<Persoan> Persoanas { get; set; }
}
[Serializable]
public class Persoan
{
public int Id { get; set; }
public String Name { get; set; }
}

How to bind a Dictionary<Int32, CustomClass> to a dropdown

I've seen lots of posts about binding a dictionary to a drop down when the value is a string.
What if the value is a class with a particular property of that class being the one thats displayed in the drop down?
Dictionary<Int32, MyClass>
// Value
class MyClass {
public String Yer="123";
public String Ner="321";
}
How do I display property Yer in my dropdown that's bound to that dictionary?
You need to use the DataTextField and DataValueField properties to the combo. Try this:
private void Page_Load(object sender, EventArgs e)
{
List<MyDummyObject> data = new List<MyDummyObject>()
{
new MyDummyObject() {ID = 1, RandomBoolValue = true, SomeRandomDescription = "First item" }
,new MyDummyObject() {ID=2, RandomBoolValue = false, SomeRandomDescription = "Second item" }
};
comboBox1.DataTextField = "SomeRandomDescription";
comboBox1.DataValueField = "ID";
comboBox1.DataSource = data;
comboBox1.DataBind();
}
private class MyDummyObject
{
public int ID { get; set; }
public string SomeRandomDescription { get; set; }
public bool RandomBoolValue { get; set; }
public override string ToString()
{
return "zzzzzz";
}
}
The overridden ToString on MyDummyObject is just to prove that it isn't being called (which is the default action if you don't specify DataTextField or DataValueField).

Categories