How do I extend a Control in WinForms? - c#

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.

Related

PropertyGrid: remove a property of a custom data type only for a specific property

First: the wording of the question may be inaccurate, sorry for that. The actual question is below all code snippets.
Second: the code is in C#, but I usually write code in VB.NET.
I have a class LabelData which contains data for the visual appearence of a user drawn label. Short example:
public class LabelData
{
public Color BackColor1 { get; set; }
public Color BackColor2 { get; set; }
public Color TextColor { get; set; }
public int TextAngle { get; set; }
public string Text { get; set; }
}
My UserControl draws a lot of text (lets call them small labels) using LabelData. It looks like this (simplified):
public class UserControl1
{
public LabelData Title { get; set; }
protected override void OnPaint(PaintEventArgs e)
{
// draw title here
LabelData currentLabel;
for (int i = 0; i <= 9; i++)
{
currentLabel = new LabelData();
currentLabel.BackColor1 = Color.Green;
currentLabel.BackColor2 = Color.YellowGreen;
currentLabel.TextAngle = 0;
currentLabel.Text = "Element" + i.ToString();
// draw text here
}
}
}
All data for the small labels are defined within OnPaint. I don't want this. I thought of a Template for the smaller labels like the DataGridViewRowTemplate. This also allows me to implement ICloneable for LabelData.
This would change to:
public class UserControl1
{
public LabelData Title { get; set; }
public LabelData LabelTemplate { get; set; }
protected override void OnPaint(PaintEventArgs e)
{
LabelData currentLabel;
for (int i = 0; i <= 9; i++)
{
currentLabel = LabelTemplate.Clone();
currentLabel.Text = "Element" + i.ToString();
}
}
}
Now the question: How can I remove the Text-Property for the LabelTemplate property (but not for the Title property) in a PropertyGrid since this property changes in OnPaint anyways?
PS: I tried to create a custom designer and overwrite PreFilterProperties to remove the Text property, but I couldn't add a DesignerAttribute to the LabelTemplate property.
Since you probably will need an ExpandableObjectConverter to present properties assigned from your LabelData Class objects, you could create a Custom TypeConverter based on ExpandableObjectConverter and remove Properties from the base Class Object, when you want to present this object differently in specific circumstances.
Here, as example, while both Title and Template properties are shown as an expandable object in the PropertyGrid (so you can edit each property value with its specific type editor), the Template property has a slightly different Type Converter, where the GetProperties() method is overridden to remove the Text property from the underlying Class object, so it won't be shown in the PropertyGrid:
The Template expandable object property doesn't show the Text property as its fellow Title property
C# version:
public class UserControl1
{
public UserControl1() {
Template = new LabelData() { ... };
Title = new LabelData() { ... };
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public LabelData Title { get; set; }
[TypeConverter(typeof(CustomExpandableConverter))]
public LabelData Template { get; set; }
// [...]
}
public class CustomExpandableConverter : ExpandableObjectConverter
{
public CustomExpandableConverter() { }
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
var props = base.GetProperties(context, value, attributes)
.OfType<PropertyDescriptor>().Where(pd => pd.Name != "Text").ToArray();
return new PropertyDescriptorCollection(props);
}
}
VB.Net version:
Public Class UserControl1
Public Sub New()
Template = New LabelData()
Title = New LabelData()
End Sub
<TypeConverter(GetType(ExpandableObjectConverter))>
Public Property Title As LabelData
<TypeConverter(GetType(CustomExpandableConverter))>
Public Property Template As LabelData
'[...]
End Class
Public Class CustomExpandableConverter
Inherits ExpandableObjectConverter
Public Sub New()
End Sub
Public Overrides Function GetProperties(context As ITypeDescriptorContext, value As Object, attributes() As Attribute) As PropertyDescriptorCollection
Dim props = MyBase.GetProperties(context, value, attributes).
OfType(Of PropertyDescriptor)().
Where(Function(pd) Not pd.Name.Equals("Text")).ToArray()
Return New PropertyDescriptorCollection(props)
End Function
End Class

No matching bindings are available, and the type is not self-bindable when using Lazy<T>

Thanks in advance for those of you taking the time to read this!
I am attempting to use Ninject to resolve the items contained in a pair of cascading listboxes named CategoryListBox and SubCategoryListBox then Lazy Load a form when the SubCategoryListBox item is clicked.
I have the following interfaces:
public interface ICategory
{
string Caption { get; set; }
ISubCategory[] SubCategories { get; set; }
}
public interface ISubCategory
{
string Caption { get; set; }
Lazy<ISubForm> SubForm { get; set; }
}
public interface ISubForm
{
void Show();
}
I have the following "Base" classes implement the interfaces:
public class BaseCategory : ICategory
{
public string Caption { get; set; }
public ISubCategory[] SubCategories { get; set; }
public BaseCategory(string caption, ISubCategory[] subCategories)
{
Caption = caption;
SubCategories = subCategories;
}
}
public class BaseSubCategory : ISubCategory
{
public string Caption { get; set; }
public Lazy<ISubForm> SubForm { get; set; }
public BaseSubCategory(string caption, Lazy<ISubForm> subForm)
{
Caption = caption;
SubForm = subForm;
}
}
I have 4 "Concrete" forms to implement the ISubForm interface as follows:
public partial class SubForm1A : Form, ISubForm {}
public partial class SubForm1B : Form, ISubForm {}
public partial class SubForm2A : Form, ISubForm {}
public partial class SubForm2B : Form, ISubForm {}
I have referenced Ninject and Ninject.Extensions.Factory via NuGet and my usings look like this
using Ninject;
using Ninject.Extensions.Factory;
My binding statements look like this:
IKernel kernel = new StandardKernel();
kernel.Bind<ICategory>().To<BaseCategory>().Named("Category 1").WithConstructorArgument("One");
kernel.Bind<ISubCategory>().To<BaseSubCategory>().WhenParentNamed("Category 1").Named("SubCategory 1A").WithConstructorArgument("1A");
kernel.Bind<ISubCategory>().To<BaseSubCategory>().WhenParentNamed("Category 1").Named("SubCategory 1B").WithConstructorArgument("1B");
kernel.Bind<ISubForm>().To<SubForm1A>().WhenParentNamed("SubCategory 1A");
kernel.Bind<ISubForm>().To<SubForm1B>().WhenParentNamed("SubCategory 1B");
kernel.Bind<ICategory>().To<BaseCategory>().Named("Category 2").WithConstructorArgument("Two");
kernel.Bind<ISubCategory>().To<BaseSubCategory>().WhenParentNamed("Category 2").Named("SubCategory 2A").WithConstructorArgument("2A");
kernel.Bind<ISubCategory>().To<BaseSubCategory>().WhenParentNamed("Category 2").Named("SubCategory 2B").WithConstructorArgument("2B");
kernel.Bind<ISubForm>().To<SubForm2A>().WhenParentNamed("SubCategory 2A");
kernel.Bind<ISubForm>().To<SubForm2B>().WhenParentNamed("SubCategory 2B");
I populate the CategoryListBox datasource as follows:
List<ICategory> categories = kernel.GetAll<ICategory>().ToList<ICategory>();
CategoryListBox.DataSource = categories;
CategoryListBox.DisplayMember = "Caption";
When you double-click the item in the CategoryListBox it populates the SubCategoryListBox as follows:
private void CategoryListBox_MouseDoubleClick(object sender, MouseEventArgs e)
{
ICategory selected = (ICategory)((ListBox)sender).SelectedItem;
SubCategoryListBox.DataSource = selected.SubCategories;
SubCategoryListBox.DisplayMember = "Caption";
}
When you double-click the item in the SubCategoryListBox I attempt to lazy load the SubForm and that is when I run into the "No matching bindings are available" error
private void SubCategoryListBox_MouseDoubleClick(object sender, MouseEventArgs e)
{
ISubCategory selected = (ISubCategory)((ListBox)sender).SelectedItem;
selected.SubForm.Value.Show();
}
My goal is to not instantiate the SubForms until I click the SubCategoryListBox.
I am fairly certain I am going about it the wrong way and any suggestions are welcomed.
I was able to resolve my issue by adding ninject.extensions.contextpreservation to my references

Showing a SelectedItem's Property

So I enabled a rightclick option for my DataGrid. I want to display just one property of the selecteditem but it's not behaving like how I would like. It displays my namespace and extra.
public class Paymentinfo
{
public int PaymentNo { get; set; }
public String Date { get; set; }
public double Payment { get; set; }
public double Principle { get; set; }
public double Interest { get; set; }
public double Balance { get; set; }
}
private void MenuItem_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show(AmortGrid.SelectedItem.ToString());
}
I am trying to implement this without using a viewmodel! If I put a breakpoint where Messagebox is and put the cursor over the selectedItem then it'll display the properties paymentNo-date-payment-principle-interest-balance. The only value I require is PaymentNo
was hoping it'd be something like this
MessageBox.Show(AmortGrid.SelectedItem.PaymentNo.ToString());
When you call ToString() like that, you get the name of the class type, which is what you're seeing.
If that's a collection of Paymentinfo, cast SelectedItem back to that type first:
MessageBox.Show(((Paymentinfo)AmortGrid.SelectedItem).PaymentNo.ToString());
FWIW, I'd reconsider the ViewModel. Far easier to test your code if you get it out of the code-behind.
You'd be able to bind your SelectedItem directly to a property in the ViewModel (perhaps called SelectedPaymentinfo), and then there'd be no messing around with casting.
You can also set the SelectedValuePath and instead of using SelectedItem use SelectedValue.
Create a ToString() method on PaymentInfo.
public class Paymentinfo
{
public override string ToString()
{
return PaymentNo.ToString();
}
}

C# Issue with subclassed property name in a user control DataBind()

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;
}

DataBinding to a WinForm

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;
}

Categories