I have the following code that I'm trying to use to populate a ComboBox, but it's not showing the actual text of the objects that I'm adding.
internal partial class SortBox : UserControl {
private Field[] FieldReferences
...
internal Field[] Fields {
...
set {
this.FieldReferences = value;
this.cboFields.Items.Clear();
string NoneString = "(none)";
this.cboFields.Items.Add(NoneString);
this.cboFields.SelectedItem = NoneString;
foreach (Field Field in this.FieldReferences) {
MessageBox.Show(Field.ToString()); // <- This displays what I want displayed perfectly.
this.cboFields.Items.Add(Field);
}
}
}
...
}
public partial class Field : UserControl {
protected string LabelValue;
...
public override string ToString() {
return this.LabelValue;
}
}
Here's what I'm getting; they're all blank:
What am I doing wrong?
EDIT: Apparently, my Field class is inheriting from UserControl. I've done some tests, and it apparently has something to do with the fact that the class inherits from System.ComponentModel.Component.
If I am reading your code right, you are trying to put a usercontrol inside a combobox.
Overriding the ToString won't work when you do that, so to make the code that you currently have work, just change the DrawMode:
This works:
cboFields.DrawMode = DrawMode.OwnerDrawFixed;
cboFields.DrawItem += new DrawItemEventHandler(cboFields_DrawItem);
private void cboFields_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
if (e.Index > -1)
e.Graphics.DrawString(cboFields.Items[e.Index].ToString(), e.Font, Brushes.Black, e.Bounds);
}
But I have to say, I don't know if putting a UserControl inside a ComboBox collection is the best way to do this. I would seriously consider refactoring that differently.
The items you add to the combo box should also be strings, just like in your message box.
Try: this.cboFields.Items.Add(Field.ToString());
Alternately, you can try setting the DisplayMember field, although ToString should already be the default:
this.cboFields.DisplayMember = "ToString()"
Related
I'm currently creating a project in Visual Studio 2015 using C# in WinForms; I used the words "ToolBox items" to refer to dateTimePickers, textbox, labels etc. It is a really big project and it will save me a lot of time if I could drag and drop the objects and they already have format; for example the datetimePicker custom format, textbox align etc.
The issue is that I need to customize some properties of these objects. This far I have this piece of code that allows me to change some properties that are overridable.
public partial class MoneyBox : TextBox
{
public override Color BackColor
{
get { return Color.Azure;}
set { base.BackColor = value; }
}
}
But for other properties I cannot do this. Also I cannot inherit from an object already formatted because this object hasn't been initialized and I get null when inheriting. I also tried to customize the initialize component and the paint event of objects, but for some reason the changes don't show on the object.
public partial class DateTimePick : DateTimePicker
{
public void InitializeComponent()
{
InitializeComponent();
this.Format = DateTimePickerFormat.Custom;
this.CustomFormat = "dd/MM/yyyy";
}
}
Any ideas?
Thanks in advance!
Constructor is a suitable place to initialize properties of the control. For most properties, to initialize the control with custom values when you drop it on design surface, it's enough to set new values in constructor, for example:
public class MyDateTimePicker : DateTimePicker
{
public MyDateTimePicker()
{
this.Format = DateTimePickerFormat.Custom;
this.CustomFormat = "dd/MM/yyyy";
}
}
In some cases, for example for Text property, when you drop an instance of the control on design surface, the property is set in InitializeNewComponent method of the Designer of control.
This may not be exactly what you're looking for, I'm sure there is a better way of doing it, but this was my solution for default control properties. It makes a list of all controls in your form (and their child controls) and changes the properties on initialization.
public static void ChangeDefaultProperties(Control C)
{
var ControlQueue = new Queue<Control>();
ControlQueue.Enqueue(C);
while (ControlQueue.Count > 0)
{
Control Current = ControlQueue.Dequeue();
DefaultPropertiesOverride(Current);
foreach (Control c in Current.Controls)
{
ControlQueue.Enqueue(c);
}
}
}
public static void DefaultPropertiesOverride(Control C)
{
if(C is DateTimePicker)
{
((DateTimePicker)C).Format = DateTimePickerFormat.Custom;
((DateTimePicker)C).CustomFormat = "dd/MM/yyyy";
}
if(C is TextBox)
{
((TextBox)C).BackColor = Color.Azure;
}
}
Then just call ChangeDefaultProperties(this); in your main form initialization
I currently have a ComboBox in my Windows Forms Application. In order to specify which values the ComboBox will contain, I set DataSource property of the ComboBox to some array so that ComboBox contains values from that array. I could also use Items.Add() to add new values to ComboBox. However, I want to make sure that ComboBox can be populated with objects of some specific type. So, if I have a class called X, then I want to make it so that only an array of type X can be used as a data source for the ComboBox. Right now, ComboBox accepts objects of type System.Object. How can I achieve it? Is there a property of ComboBox that I need to set to be equal to my data type's name? Or is there an event that will check whether an object added to my ComboBox is of the needed type and will throw an exception if not?
I was thinking of creating a new class as a subtype of ComboBox, and overriding the Add method of Items property so that Add checks whether its argument is of the needed type (not sure if and how I can do it). Even if I do that, there are still other ways to add new values into ComboBox (AddRange, CopyTo, etc.), so I think there should be a more elegant solution to this problem.
If you want to control the type of item that the ComboBox can contain, you could try creating a new class derived form ComboBox, but you'd run into the problem that it still has the ComboBox.ObjectCollection Items property which would still accept any type! And (unfortunately for your idea of overriding) the Add method isn't virtual.
The only practical solution that I could think of would be to abstract the ComboBox somehow. If this isn't shared code, I would recommend just creating a method that you would use to add items to the ComboBox. Something like:
// NOTE: All items that are added to comboBox1 need to be of type `SomeType`.
private void AddItemToComboBox(SomeType item)
{
comboBox1.Items.Add(item);
}
Any attempt to add a non-SomeType object to the ComboBox would be met with a compiler error. Unfortunately, there's no easy way to prevent someone from still adding a non-SomeType item to ComboBox.Items directly.
Again, if this isn't shared code, it shouldn't really be an issue.
You can hide Items property by your
own Items property of custom type which taking as parameter original ItemsCollection
Example class for testing
public class Order
{
public Int32 ID { get; set; }
public string Reference { get; set; }
public Order() { }
public Order(Int32 inID, string inReference)
{
this.ID = inID;
this.Reference = (inReference == null) ? string.Empty : inReference;
}
//Very important
//Because ComboBox using .ToString method for showing Items in the list
public override string ToString()
{
return this.Reference;
}
}
With next class I tried wrap ComboBox's items collection in own type.
Where adding items must be concrete type
Here you can add other methods/properties you need (Remove)
public class ComboBoxList<TCustomType>
{
private System.Windows.Forms.ComboBox.ObjectCollection _baseList;
public ComboBoxList(System.Windows.Forms.ComboBox.ObjectCollection baseItems)
{
_baseList = baseItems;
}
public TCustomType this[Int32 index]
{
get { return (TCustomType)_baseList[index]; }
set { _baseList[index] = value; }
}
public void Add(TCustomType item)
{
_baseList.Add(item);
}
public Int32 Count { get { return _baseList.Count; } }
}
Here custom combobox class derived from ComboBox
Added: generic type
public class ComboBoxCustomType<TCustomType> : System.Windows.Forms.ComboBox
{
//Hide base.Items property by our wrapping class
public new ComboBoxList<TCustomType> Items;
public ComboBoxCustomType() : base()
{
this.Items = new ComboBoxList<TCustomType>(base.Items);
}
public new TCustomType SelectedItem
{
get { return (TCustomType)base.SelectedItem; }
}
}
Next code used in the Form
private ComboBoxCustomType<Order> _cmbCustom;
//this method used in constructor of the Form
private void ComboBoxCustomType_Initialize()
{
_cmbCustom = new ComboBoxCustomType<Order>();
_cmbCustom.Location = new Point(100, 20);
_cmbCustom.Visible = true;
_cmbCustom.DropDownStyle = ComboBoxStyle.DropDownList;
_cmbCustom.Items.Add(new Order(0, " - nothing - "));
_cmbCustom.Items.Add(new Order(1, "One"));
_cmbCustom.Items.Add(new Order(2, "Three"));
_cmbCustom.Items.Add(new Order(3, "Four"));
_cmbCustom.SelectedIndex = 0;
this.Controls.Add(_cmbCustom);
}
Instead of overriding ComboBox (which wont work as stated in itsme86's answer) you could override usercontrol, add a combobox to this, and then only expose the elements that you wish to work with. Something similar to
public partial class MyComboBox<T> : UserControl where T: class
{
public MyComboBox()
{
InitializeComponent();
}
public void Add(T item)
{
comboBox1.Items.Add(item);
}
public IEnumerable<T> Items
{
get { return comboBox1.Items.Cast<T>(); }
}
}
Please note however that some pieces of automated software rely on access the the underlying controls however so this may cause some issues.
This approach never changes the Items of the combobox so they will still store as objects but when you access them, you are casting them to the correct type and only allowing them to be added of that type. You can create a new combobox via
var myCB = new MyComboBox<ItemClass>();
This might be a duplicate question, but I'm unable to find a good answer. All the answers like Binding WinForms ListBox to object properties don't work on my WinForm. I'll explain.
I have a list of Firms that I show in a ListBox. I would like when the SelectedItem changes, that it updates a property on my model. So that I can read the Firms properties.
// the classes
public class Firm
{
public string Name { get; set; }
public int Id { get; set; }
// more properties ...
}
public class MyModel : INotifyPropertyChanged
{
private Firm _firm = new Firm();
public Firm Firm
{
get { return _firm; }
set
{
if (Equals(value, _firm)) return;
_firm = value;
OnPropertyChanged();
}
}
// more properties and OnPropertyChanged() ...
}
// the form
private MyModel Model;
public void MyForm(List<Firm> firms)
{
lstFirm.DataBindings.Add("SelectedItem", Model, "Firm",
true, DataSourceUpdateMode.OnPropertyChanged);
lstFirm.DisplayMember = "Name";
lstFirm.ValueMember = "Id";
lstFirm.DataSource = firms;
}
public void lstFirm_SelectedIndexChanged(object sender, EventArgs e)
{
// Do something with Model.Firm
}
The problem is that Model.Firm null is. Does anybody have an idea what I need to do to make a databinding between the ListBox and the Model? I bind other stuff on my WinForm (such as TextBoxes to String properties) and those work nicely.
From what I can see, your code never sets Model.Firm... Where's the constructor for MyModel? If you don't provide one, Model.Firm will stay null unless you explicitly set it. Here's an example constructor:
public MyModel(Firm firm)
{
_firm = firm;
}
Also, Equals() doesn't do what you think it does. Instead of if (Equals(value, _firm)) return;, use this: if (value == _firm) return;
Ok, so after a weekend of testing, I figured it out.
I was debuging in the SelectedIndexChanged event and didn't see the change in my Model.Firm just yet. But as the SelectedItemChanged event is only internal, I couldn't use that and that's where the databinding on SelectedItem applies the values to databound items.
Now the reason why the change isn't visible yet, is because the SelectedItemChanged is only fired after the SelectedIndexChanged is executed. So internally in the ListBox control, it probably looks like
this.SelectedIndex = value;
this.SelectedItem = FindItem(value);
this.SelectedIndexChanged(/*values*/);
this.SelectedItemChanged(/*values*/); // Apply databinding changes
So it's quite normal that you don't see the changes, before the change has occured. And I didn't know this, so I was kinda stumped why the SelectedItem (who was displaying the changed value) wasn't copied over to the databound model property.
So I didn't have to change anything major to get it all working. :)
I have a list of custom objects which I have added to a ListBox control in my WinForms C# 4.0 application.
When the user selects a particular element in the ListBox, the properties of that object come up in the window next to the ListBox in various input fields. The user can change these and click 'Save' which will modify the data members of the objects to correspond with the changes the user has made.
The function does work. The values are saved to the object, and when the user selects the element again, their changes are confirmed to be saved correctly.
What isn't working is the update of the text in the ListBox. For example if we have a list of staff in the ListBox, and we can see "John Smith" there, we can click his name - edit his name to "John Smithe" and click OK. The ListBox still shows "John Smith", however if we click on his name, then in the TextBoxes on the right we can see that his name has correctly been changed to "John Smithe".
I have tried calling the Refresh() method on the ListBox but this didn't work.
I can fix it by removing the item from the ListBox and adding it again. This works, and it's not really an issue because the items are stored in separate lists anyway so I have no risk of losing any of my staff.
But is this really the best way to do it? Is there a more elegant way to update the text in the ListBox without removing/adding the item again?
Do the objects in the ListBox implement INotifyPropertyChanged?
Update:
It seems that you can solve the problem with a couple of steps:
Set the DisplayMember property of the ListBox to a property on your objects that provides whatever it is you want to appear in the list. I will assume this property is named DisplayText for this answer.
Have the objects implement INotifyPropertyChanged.
In the setters of all the properties that influence the value of DisplayText, raise the NotifyPropertyChanged event with DisplayText for the property name.
You should then be good to go.
Following the tutorial I reference above I made a quick and dirty example of using a BindingList. Hopefully it's helpful to you.
public partial class Listbox_Databinding : Form
{
BindingList<Person> People = new System.ComponentModel.BindingList<Person>();
public Listbox_Databinding()
{
InitializeComponent();
People.Add(new Person("John", "Smith"));
People.Add(new Person("John", "Jacob"));
lstSelectPerson.DataSource = People;
}
private void lstSelectPerson_SelectedIndexChanged(object sender, EventArgs e)
{
txtLast.Text = ((Person)lstSelectPerson.SelectedItem).Last;
}
private void btnUpdate_Click(object sender, EventArgs e)
{
((Person)lstSelectPerson.SelectedItem).Last = txtLast.Text;
}
}
public class Person : INotifyPropertyChanged
{
public Person(string first, string last)
{
First = first;
Last = last;
}
public override string ToString()
{
return Last + ", " + First;
}
string p_first;
string p_last;
public string First
{
get { return p_first; }
set
{
p_first = value;
OnDisplayPropertyChanged();
}
}
public string Last
{
get { return p_last; }
set
{
p_last = value;
OnDisplayPropertyChanged();
}
}
void OnDisplayPropertyChanged()
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("DisplayName"));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I have a numericupdown control on a C# Windows Form, and am interested in adding a leading zero to its value if it is < 10. (It is for the user to enter the minutes value of a time.)
I am not very familiar with overrides/inheriting yet in C# but it is looking as though I may have to do that.
It looks like this post on EggheadCafe has the answer I need. Is it as simple as making a new class and then creating a control of that new class?
public class TestNum : NumericUpDown
{
protected override void ValidateEditText()
{
if (base.UserEdit)
{
base.ValidateEditText();
}
}
protected override void UpdateEditText()
{
Text = Convert.ToInt32(base.Value).ToString("00");
}
}
When I try this, I am not sure how to create the new control that takes advantage of this class. I am using Visual Studio 2008. Still very new to windows forms. Thanks for any advice.
EDIT
I was able to make this work by editing the Designer-created code so that instead of the new control being of the original class, it was of the new one. So after adding the class above, I did the following (these changes are in two different locations, but I am only showing the lines that mattered):
Changed:
this.numTest = new System.Windows.Forms.NumericUpDown();
private System.Windows.Forms.NumericUpDown numTest;
To:
this.numTest = new SampleForm.TestNum();
private TestNum numTest;
Why not just use a DateTimePicker control? Set its ShowNumericUpDown property to true and set its Format property to Custom and set the CustomFormat property to hh:mm:ss.
Perhaps this will be useful for you.
You need to use this newly created class in your form. It doesn't replace all NumericUpDown controls, it's a subclass.
Add the project which contains this class to the toolbox (Tools->Toolbox Items... - if memory serves) and you should be able to drag the control onto the form where you want to use it.
Stumbled across this by looking for "NumericUpDown Display Hex with Leading Zero's"
public class HexNumericUpDown : System.Windows.Forms.NumericUpDown
{
public HexNumericUpDown()
{
Hexadecimal = true;
}
protected override void ValidateEditText()
{
if (base.UserEdit)
{
base.ValidateEditText();
}
}
protected override void UpdateEditText()
{
Text = System.Convert.ToInt64(base.Value).ToString("X" + HexLength);
}
[System.ComponentModel.DefaultValue(4)]
public int HexLength
{
get { return m_nHexLength; }
set { m_nHexLength = value; }
}
public new System.Int64 Value
{
get { return System.Convert.ToInt64(base.Value); }
set { base.Value = System.Convert.ToDecimal(value); }
}
private int m_nHexLength = 4;
}
Use date time picker with following properties:
Format = Custom
CustomFormat = "hh:mm:ss"
ShowUpDown = True