I have a form (CustomerInfoForm) with 10 TextBoxes. The default Text property for each of the TextBoxes is defined at design-time. A subclass CustomerInfoForm.CustomerInfo contains properties to hold the data entered in the form. The subclass containing the data will be serialized to XML.
In the automatically generated form code, each of the text boxes has a line of code to bind the datasource to the text box
this.customerInfoBindingSource = new System.Windows.Forms.BindingSource(this.components);
Code automatically generated by the C# ide for each text box:
this.txtCustomer.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.customerInfoForm_CustomerInfoBindingSource, "CustomerName", true));
this.txtCustomer.Location = new System.Drawing.Point(60, 23);
this.txtCustomer.Name = "txtCustomer";
this.txtCustomer.Size = new System.Drawing.Size(257, 20);
this.txtCustomer.TabIndex = 0;
this.txtCustomer.Text = "CustomerName";
(I noticed that the Text property isn't set until after the DataBinding) in the IDE generated code.
When I run the project, the form is displayed with the default values in the TextBoxes. However when the SaveButton is pressed to serialize the properties in the MyForm.CustomerInfo subclass, they are all null. Since these values will only be changed from the form I was hoping that I didn't have to implement the interface INotifyPropertyChanged.
Am I missing something basic or simple?
The code for the form including the serialization of the data is attached below
using System;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization;
namespace SimpleCustomerInfo
{
// You must apply a DataContractAttribute or SerializableAttribute
// to a class to have it serialized by the DataContractSerializer.
public partial class CustomerInfoForm : Form
{
CustomerInfo ci = new CustomerInfo();
public CustomerInfoForm()
{
InitializeComponent();
}
private void btnSave_Click(object sender, EventArgs e)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(CustomerInfo));
FileStream writer = new FileStream(#"C:\Users\Me\temp\testme.xml", FileMode.Create);
serializer.WriteObject(writer,ci);
writer.Close();
}
[DataContract(Name = "Customer", Namespace = "net.ElectronicCanvas")]
public class CustomerInfo
{
[DataMember]
public string CustomerName { get; set; }
[DataMember]
public PhoneInfo PhonePrimary { get; set; }
[DataMember]
public PhoneInfo PhoneDays { get; set; }
[DataMember]
public PhoneInfo PhoneEvening { get; set; }
}
public class PhoneInfo
{
public string number { get; set; }
public string type { get; set; }
public bool textOk { get; set; }
}
}
}
EDIT -- For others that may happen upon this question
using System;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization;
namespace SimpleCustomerInfo
{
public partial class CustomerInfoForm : Form
{
CustomerInfo ci;
public CustomerInfoForm()
{
InitializeComponent();
ci = new CustomerInfo();
ci.CustomerName = "My Customer Name";
ci.PhoneDays.number = "888-888-8888";
customerInfoForm_CustomerInfoBindingSource.DataSource = ci;
}
private void btnSave_Click(object sender, EventArgs e)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(CustomerInfo));
FileStream writer = new FileStream(#"C:\Users\me\temp\testme.xml", FileMode.Create);
serializer.WriteObject(writer,ci);
writer.Close();
}
// You must apply a DataContractAttribute or SerializableAttribute
// to a class to have it serialized by the DataContractSerializer.
[DataContract(Name = "Customer", Namespace = "net.ElectronicCanvas")]
public class CustomerInfo
{
[DataMember]
public string CustomerName { get; set; }
[DataMember]
public PhoneInfo PhonePrimary { get; set; }
[DataMember]
public PhoneInfo PhoneDays { get; set; }
[DataMember]
public PhoneInfo PhoneEvening { get; set; }
// Constructor is needed to instantiate the PhoneInfo classes
// within the CustomerInfo class
public CustomerInfo()
{
PhonePrimary = new PhoneInfo();
PhoneDays = new PhoneInfo();
PhoneEvening = new PhoneInfo();
}
}
public class PhoneInfo
{
public string number { get; set; }
public string type { get; set; }
public bool textOk { get; set; }
}
}
}
First of all you need to decide whether to use databinding or manipulate Text property directly. Those two approaches should not be mixed together.
If you want to use databinding than you are missing one line in the code:
public Form1()
{
InitializeComponent();
customerInfoBindingSource.DataSource = ci; // This is the missing line
}
You need to let your customerInfoBindingSource know about the data source.
If you add this line, then the Text that is assigned in design time will be overridden by the text from your bound data source. If you want to use binding you should manipulate with the data source instead of setting Text fields directly. Like this:
public Form1()
{
InitializeComponent();
ci.CustomerName = "TestCustomerName";
customerInfoBindingSource.DataSource = ci;
}
Related
I am trying to pull the properties from the PullConstants class to the CreateForecast class. In CreateForecast, I have created an instance of PullConstants with this code. I have also verified that both classes are in the same namespace.
PullConstants pc = new PullConstants();
However, when I try to pull a value from PullConstants to CreateForecast with code below, I always receive 0.
double sla = pc.sla;
I have verified that the value gets pulled from the database correctly, but its scope seems to not reach beyond the first run of the class. What exactly am I doing wrong that I am not able to pull the correct property value from PullConstants?
The PullConstants class is as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MySql.Data.MySqlClient;
namespace ForecastBuilder
{
class PullConstants
{
InitializeDB idb = new InitializeDB();
ErrorLogger el = new ErrorLogger();
public double sla { get; set; }
public int serviceTime { get; set; }
public int avgHandleTime { get; set; }
public int maxWait { get; set; }
public double shrinkageAdjustment { get; set; }
public double alpha { get; set; }
public double beta { get; set; }
public double specialDayPerInc { get; set; }
public void PullConstantValues()
{
idb.OpenDatabases();
try
{
string sqlConstants = "select * from forecastconstants";
MySqlCommand cmdConstants = new MySqlCommand(sqlConstants, idb.myconn);
MySqlDataReader rdrConstants = cmdConstants.ExecuteReader();
while (rdrConstants.Read())
{
sla = double.Parse(rdrConstants["SLA"].ToString());
serviceTime = int.Parse(rdrConstants["ServiceTime"].ToString());
avgHandleTime = int.Parse(rdrConstants["AvgHandleTime"].ToString());
maxWait = int.Parse(rdrConstants["MaxWait"].ToString());
shrinkageAdjustment = double.Parse(rdrConstants["ShrinkageAdjustment"].ToString());
alpha = double.Parse(rdrConstants["Alpha"].ToString());
beta = double.Parse(rdrConstants["Beta"].ToString());
specialDayPerInc = double.Parse(rdrConstants["SitCallIncrPer"].ToString());
}
}
catch (Exception e)
{
el.createError(2, e.ToString(), "Could not pull constants");
}
finally
{
idb.myconn.Close();
}
}
}
}
I guess you are missing the call to PullConstantValues:
PullConstants pc = new PullConstants();
pc.PullConstantValues();
double sla = pc.sla;
If supplying these values is the only purpose of this class you may are better of using it as a constructor:
class PullConstants
{
/* ... */
public PullConstants() // instead of 'void PullConstantValues()'
{
/* ... */
}
}
If the are "real constants" may also use a singleton to not query the DB every time.
Either you are misssing a call to this function
public void PullConstantValues();
or you may consider making this function a constructor by changing it to
public PullConstant();
Say we have a simple class model with classes as feilds (inside compiled, not modifiable Dll):
public class SubSubClassTest {
public int Data { get; set; }
}
public class SubClassTest {
public string InnerStr { get; set; }
public int InnerInteger { get; set; }
public SubSubClassTest InnerLoad { get; set; }
public SubClassTest() {
InnerLoad = new SubSubClassTest();
}
}
public class Test {
public string Str { get; set; }
public int Integer { get; set; }
public SubClassTest Load { get; set; }
public Test() {
Load = new SubClassTest();
}
}
And we want to edit it using PropertyGrid.
public partial class ApplicationForm : Form {
public ApplicationForm() {
InitializeComponent();
var test = new Test();
propertyGrid.SelectedObject = test;
}
}
And I do not have abilety to change classes (as I get them from Dll) and they have no [TypeConverter(typeof(ExpandableObjectConverter))] attribute on all members that are classes I get sush picture:
And members that are from my namespace class type are not editable.
If all such members havd [TypeConverter(typeof(ExpandableObjectConverter))] attribute I would have another picture and all would be fine:
I wonder how to make PropertyGrid use PropertyGrid for all nested classes?
You could try changing the TypeConverterAttribute value using PropertyDescriptor and Reflection. I wouldn't recommend to do this but to show that its possible I have added the sample code. I verified with your example and it works. But I cannot assure that it would work in all scenarios. Food for thought...
var test = new Test();
SetTypeConverterAttribute(test);
propertyGrid.SelectedObject = test;
private void SetTypeConverterAttribute(Test test)
{
foreach (PropertyDescriptor item in TypeDescriptor.GetProperties(test))
{
TypeConverterAttribute attribute = item.Attributes[typeof(TypeConverterAttribute)] as TypeConverterAttribute;
if (attribute != null && item.PropertyType == typeof(SubClassTest))
{
FieldInfo field = attribute.GetType().GetField("typeName", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
{
field.SetValue(attribute, typeof(ExpandableObjectConverter).FullName);
}
}
}
}
If you have control over the classes, you can create a common base class and decorate this base class with the TypeConverterAttribute. In that case, any property that will reference any instance of this type will use the ExpandableObjectConverter, unless this behavior is overridden by the property (using another TypeConverterAttribute).
I'm creating my own user control in a asp.net project and have two asp list elements in this control that I want to set a data source from another class' list objects.
My exception error is:
DataBinding: 'TeamTracker.Flash.ErrorMessage' does not contain a property with the name 'message'.
The user control looks like this:
public partial class flashMessage : System.Web.UI.UserControl
{
protected void Page_PreRender (object sender, EventArgs e)
{
TTPage page = (TTPage)this.Page;
WebFlash flash = page.flashMessages;
messages.DataSource = flash.NoticeMessages;
messages.DataTextField = "message";
messages.DataValueField = "name";
messages.DataBind();
errorMessages.DataSource = flash.ErrorMessages;
errorMessages.DataTextField = "message";
errorMessages.DataValueField = "name";
errorMessages.DataBind();
}
}
ErrorMessage is the class bellow:
namespace TeamTracker.Flash
{
public class ErrorMessage : Message
{
public ErrorMessage(string message)
{
this.message = message;
}
}
}
The message property is defined in the parent abstract class
namespace TeamTracker.Flash
{
public abstract class Message
{
public string name, message, colour;
}
}
Is there a reason why the data Bind cant see this property "message"? The error occurred on the errorMessages bind as it had two objects in the list as NoticeMessages had 0.
You need to use public properties for databinding,
public abstract class Message
{
public string Name { get; set; }
public string Message { get; set; }
public string Colour { get; set; }
}
if you can't change the fields to properties, then do as below.
errorMessages.DataSource = flash.ErrorMessages.Select(x=>
new { Message = x.message, Name = x.name}).ToList();
errorMessages.DataTextField = "Message";
errorMessages.DataValueField = "Name";
It looks like the binding expects a property. name, message and colour are fields.
What it looks like you need is something like this:
public string name
{
get;
set;
}
public string message
{
get;
set;
}
public string colour
{
get;
set;
}
Any control. But preferably all of them (TextBox, Panel, Button, LinkLabel, TabControl, etc). What I would like to do is:
public class Something
{
public String isBetterThan { get; set; }
public String Author { get; set; }
}
public void button1_Click(object sender, EventArgs e)
{
panelControl1.ClassObject = new Something()
{
isBetterThan = "nothing.",
Author = "Unknown"
};
}
So from the code above, you can see that it acts similarly to the .Location property, where you assign it a new value. I would like to store this information, so that later on, I can simply do this:
public void getClassDetailsButton_Click(object sender, EventArgs e)
{
Something something = (Something)panelControl1.ClassObject;
MessageBox.Show("Something is better than " + something.isBetterThan);
}
You can create a Custom control by inheriting from the control you are trying to add the function to. Something like this should work (using a button as an example)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
class SomethingButton : Button
{
public Something mySomething
{ get; set; }
}
public class Something
{
public String isBetterThan { get; set; }
public String Author { get; set; }
}
}
Usuage
somethingButton1.mySomething = new Something() { isBetterThan = "nothing",
Author = "Unknown"
};
I think the answer you are looking for is the DefaultValue attribute. You set it once in the class and then whenever the object is created it gets assigned the value. There's a couple of pitfalls with saving the object data using this attribute so use caution and do regression testing.
http://msdn.microsoft.com/en-us/library/system.componentmodel.defaultvalueattribute(v=VS.71).aspx
[DefaultValue("Author Name")]
You can always create a class that extends off of the control, and override the methods that you'd like to add functionality to.
public class MyTextBox : TextBox {
public String isBetterThan { get; set;}
public String author {get; set;}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
// do something
isBetterThan = this.Text;
}
}
Then, add the control to your Form. You can treat it like a regular TextBlock, but also ask it for isBetterThan and Author.
Can anyone please explain the theory on creating a loosely coupled viewmodel.
I have attached some example code below to try and explain what I mean.
I have 2 example classes just for this example
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Web.UI.Models
{
public class EmployerAddress
{
public string address { get; set; }
public string city { get; set; }
public string region { get; set; }
public string country { get; set; }
public string postZipCode { get; set; }
}
public class EmployerDetails
{
public string position { get; set; }
public string gender { get; set; }
public string dob { get; set; }
}
public class DisplayEmployerAddress : IDisplayEmployerAddress
{
public IEnumerable<EmployerAddress> employerAddr()
{
List<EmployerAddress> Data = new List<EmployerAddress>();
Data.Add(new EmployerAddress
{
address = "address1",
city = "city1",
region = "region1",
country = "country1",
postZipCode = "post zip1"
});
return Data;
}
}
public class DisplayEmployerDetails : IDisplayEmployerDetails
{
public IEnumerable<EmployerDetails> employerDetails()
{
List<EmployerDetails> Data = new List<EmployerDetails>();
Data.Add(new EmployerDetails
{
position = "trainee",
gender = "male",
dob = "22-08-1964"
});
Data.Add(new EmployerDetails
{
position = "trainee2",
gender = "male2",
dob = "22-08-1970"
});
return Data;
}
}
}
The code above has the interfaces:
IEnumerable<EmployerAddress> employerAddr();
IEnumerable<EmployerDetails> employerDetails();
I then use Ninject to bind the above.
kernel.Bind<IDisplayEmployerAddress>().To<DisplayEmployerAddress>().InSingletonScope();
kernel.Bind<IDisplayEmployerDetails>().To<DisplayEmployerDetails>().InSingletonScope();
At this point everything is OK, I could just change DisplayEmployerAddress etc and so long as all the methods etc match up the code will still work.
I then create a viewmodel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Web.UI.Models
{
public class EmployerDetailsViewModel
{
public string age { get; set; }
public IEnumerable<EmployerAddress> EmployerAddress { get; set; }
public IEnumerable<EmployerDetails> EmployerDetails { get; set; }
}
}
But now this would cause a problem as EmployerAddress is now tightly coupled, so if I change the code, it will now have to be updated in 2 places.
In my controller I have
public class HomeController : Controller
{
private readonly IDisplayEmployerAddress _address;
private readonly IDisplayEmployerDetails _details;
public HomeController(IDisplayEmployerAddress address,
IDisplayEmployerDetails details)
{
_address = address;
_details = details;
}
public ActionResult Index()
{
ViewBag.Title = "Title";
var Address = _address.employerAddr();
var Details = _details.employerDetails().AsEnumerable();
var Age = _details.employerDetails().FirstOrDefault().dob;
var employerModel = new EmployerDetailsViewModel
{
EmployerAddress = Address,
EmployerDetails = Details,
age = age.calAge(Age)
};
return View(employerModel);
}
I keep the controller lightweight as all the books I read say keep as little code as possible in the controller, so to calculate age I use a static class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Web.UI.Models
{
public static class age
{
public static string calAge(string dob)
{
//Would cal age here
return "48";
}
}
}
So my question is 3 parts.
Is this example the correct way.
As my viewmodel is now tightly coupled, how do I make it loosley
coupled.
If I did not want to use a foreach loop how can I get each item out
of say EmployerDetails
This employer holds the position of #Model.EmployerDetails.position the gender is #Model.EmployerDetails.gender
<ul>
#foreach (var d in Model.EmployerAddress)
{
<li>#d.address</li>
<li>#d.city</li>
<li>#d.country</li>
<li>#d.region</li>
<li>#d.postZipCode</li>
}
</ul>
<ul>
#foreach (var dd in Model.EmployerDetails)
{
<li>#dd.position</li>
<li>#dd.gender</li>
<li>#dd.dob</li>
}
</ul>
So far This employer holds the position of #Model.EmployerDetails.position the gender is #Model.EmployerDetails.gender
Worked out question 3, changed code to #Model.EmployerDetails.FirstOrDefault().position
Hope the above example makes sense on what I'm trying to learn
Thanks
George
public class Employer
{
public int Id { get; set; }
}
public class EmployerAddress
{
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string Country { get; set; }
public string PostZipCode { get; set; }
public int EmployerId { get; set; }
}
public class EmployerDetails
{
public string Position { get; set; }
public string Gender { get; set; }
public string Dob { get; set; }
public int EmployerId { get; set; }
}
public class MyRepository : IMyRepository
{
public IEnumerable<Employer> GetEmployers()
{
return new List<Employer>
{
new Employer {Id = 1},
new Employer {Id = 2}
};
}
public IEnumerable<EmployerAddress> GetEmployeeAddresses()
{
return new List<EmployerAddress>
{
new EmployerAddress
{
EmployerId = 1,
Address = "address1",
City = "city1",
Region = "region1",
Country = "country1",
PostZipCode = "post zip1"
},
new EmployerAddress
{
EmployerId = 2,
Address = "address2",
City = "city2",
Region = "region2",
Country = "country2",
PostZipCode = "post zip2"
}
};
}
public IEnumerable<EmployerDetails> GetEmployeeDetails()
{
return new List<EmployerDetails>
{
new EmployerDetails
{
EmployerId = 1,
Position = "trainee",
Gender = "male",
Dob = "22-08-1964"
},
new EmployerDetails
{
EmployerId = 2,
Position = "trainee2",
Gender = "male2",
Dob = "22-08-1970"
}
};
}
}
public class EmployerChangedEvent
{
public EmployerChangedEvent(Employer selectedEmployer)
{
Employer = selectedEmployer;
}
public Employer Employer { get; set; }
}
public class EmployerViewModel
{
private readonly IEventAggregator _events;
private Employer _selectedEmployer;
// Configure Ninject properly to get those types
public EmployerViewModel(IEventAggregator events, IMyRepository myRepository)
{
_events = events;
Employers = myRepository.GetEmployers().ToList();
EmployerAddressViewModel = new EmployerAddressViewModel(_events, myRepository);
EmployerDetailsViewModel = new EmployerDetailsViewModel(_events, myRepository);
}
public List<Employer> Employers { get; set; }
public EmployerAddressViewModel EmployerAddressViewModel { get; set; }
public EmployerDetailsViewModel EmployerDetailsViewModel { get; set; }
public Employer SelectedEmployer
{
get { return _selectedEmployer; }
set
{
_selectedEmployer = value;
// this notifies the dependent view models in a loosley coupled way
_events.Publish(new EmployerChangedEvent(_selectedEmployer));
}
}
}
public class EmployerAddressViewModel :
IHandle<EmployerChangedEvent> // specifies which events shall be caught
{
private readonly IMyRepository _myRepository;
private Employer _selectedEmployer;
public EmployerAddressViewModel(IEventAggregator events, IMyRepository myRepository)
{
_myRepository = myRepository;
// this subscribes this view model to the passed event aggregator
// from your main view model (EmployerViewModel)
events.Subscribe(this);
}
public EmployerAddress EmployerAddress { get; set; }
public void Handle(EmployerChangedEvent message)
{
_selectedEmployer = message.Employer;
EmployerAddress = _myRepository.GetEmployeeAddresses()
.FirstOrDefault(e => e.EmployerId == _selectedEmployer.Id);
}
}
public class EmployerDetailsViewModel :
IHandle<EmployerChangedEvent> // specifies which events shall be caught
{
private readonly IMyRepository _myRepository;
private Employer _selectedEmployer;
public EmployerDetailsViewModel(IEventAggregator events, IMyRepository myRepository)
{
_myRepository = myRepository;
// this subscribes this view model to the passed event aggregator
// from your main view model (EmployerViewModel)
events.Subscribe(this);
}
public EmployerDetails EmployerDetails { get; set; }
public void Handle(EmployerChangedEvent message)
{
_selectedEmployer = message.Employer;
EmployerDetails = _myRepository.GetEmployeeDetails()
.FirstOrDefault(e => e.EmployerId == _selectedEmployer.Id);
}
}
internal class Program
{
private static void Main(string[] args)
{
// do this with Ninject
var employerViewModel = new EmployerViewModel(new EventAggregator(), new MyRepository());
// this selection should actually be user input
employerViewModel.SelectedEmployer = employerViewModel.Employers.First();
// select another one
employerViewModel.SelectedEmployer = employerViewModel.Employers.Last();
}
}
As I am not familiar with ASP.NET, my answer doesn't imply any of UI notifications.
I suggest Caliburn.Micro's event aggregator class here, because it solves your coupling problem nicely. This library is worth a look anyway for learning the MVVM pattern.
The IEventAggregator allows you to subscribe with instance of a class to an instance of the aggregator. If multiple view models share an instance of the event aggregator you can easily send events from one to another in a loosley coupled way.
I refactored your original code, to make it more fitting for the actual MVVM pattern (your first question, let's say this implementaion is more proper). I've added an Employer class, which is basically the main object. It only has an id. The EmployerDetails and EmployerAddress also have a new property EmployerId which is a reference to the Employer they belong to.
I have put all the stuff to query data in the MyRepository class!
For each of those three classes exist three seperate view models and they're only coupled through the event aggregator they share (answers your 2nd question). The EmployerViewModel manages the main data objects of type Employer and publishes an event as soon as the selected Employer changes. The new value is passed into the EmployerChangedEvent which then is caught by the view models which handle this certain kind of event (IHandle<EmployerChangedEvent). In their Handle() implementation the passed employer is put into a private field of the receiving view model.
This is just a console application which simulates user input, though try with putting break points on both of the handle methods, as the SelectedEmployer changes.
I think some kind of stuff I do in my Main() method should be done in your controllers. I have to mention that this code is just for showing the benefits of the MVVM pattern, it might be over abstracted in some cases. Also things like querying the repository efficiently are not covered at all!
I think my answer also solves your 3rd question, as I see no foreach is anymore needed.
Remember to reference Caliburn.Micro if you'd like to run this code. Just get it through NuGet or download it here.