Prevent editing items within dropdown element of propertygrid - c#

I have the following code which displays a dropdown list of selectable items in a propertygrid which generally works fine. However, the dropdown allows the items within the dropdown to be edited which causes an error on 'System.ComponentModel.EnumConverter.ConvertFrom' as its not a valid enum. For example, Option1 can be changed to OptionABC1 which I want to prevent.
There is a flag on PropertyStoreItem to set it to read only but this prevents the whole property being changed rather than preventing editing of the dropdown items.
How do I make the dropdown non-editable but still allow the fixed list to be selected? It might be a property on the propertygrid I need to change but cannot find it.
[Flags]
Public Enum SomePropertyTypes
{
Option1 = 1,
Option2 = 2,
Option3 = 4,
Option4 = 8,
Option5 = 16,
Option6 = 32
}
public partial class AddSomePropertyForm : RadForm
{
private RadPropertyStore store;
Public AddSomePropertyForm()
{
InitializeComponent();
this.store = this.CreatePropertyStore();
this.radPropertyGrid1.SelectedObject = store;
}
private RadPropertyStore CreatePropertyStore()
{
RadPropertyStore somePropertyStore = new RadPropertyStore();
PropertyStoreItem somePropertyType = new PropertyStoreItem(typeof(SomePropertyTypes), "PropertyName", SomePropertyTypes.Option1, "Property Info", "Group1", false);
somePropertyStore.Add(somePropertyType);
return somePropertyStore;
}
}

You should customize the drop down editor behavior by using the EditorInitialized event.
void radPropertyGrid1_EditorInitialized(object sender, PropertyGridItemEditorInitializedEventArgs e)
{
PropertyGridDropDownListEditor editor = e.Editor as PropertyGridDropDownListEditor;
if (editor != null)
{
editor.DropDownStyle = RadDropDownStyle.DropDownList;
}
}
Here is an article on the matter: link

Set the combobox' DropDownStyle property to ComboBoxStyle.DropDownList - it sounds that is is currently set to ComboBoxStyle.DropDown, which allows editing.
Refer to ComboBoxStyle Enumeration

Related

Hide controls according to rows from database

I have 100 sequential buttons and checkboxes showed in a Windows Forms application, and a database where some numbers are saved.
My aim is to hide the buttons and checkboxes according to the number saved in the database.
For example, in my database I have 4 numbers: 2, 4, 9, and 10. So I want to hide button2, checkbox2, button4, checkbox4, button9, checkbox9, button10, checkbox10.
Here's what I tried:
SqlCeCommand cmnd = con.CreateCommand();
cmnd.CommandText = "SELECT * FROM register_db WHERE semester = #s AND department = #d AND course = #c";
cmnd.Parameters.AddWithValue("#s", semester);
cmnd.Parameters.AddWithValue("#d", department);
cmnd.Parameters.AddWithValue("#c", course);
SqlCeDataReader rd = cmnd.ExecuteReader();
while (rd.Read())
{
string number = rd[0];
button[number].hide();
checkbox[number].hide();
// these are the main things that I didn't know how to do
}
Assuming your controls are named like that, you can access them through the form’s Controls collection:
string number = rd[0];
this.Controls["button" + number].Hide();
this.Controls["checkbox" + number].Hide();
But you should really put them in a separate list, and probably group them into panels in a StackedPanel, or a CheckedListBox.
All Windows Forms controls have a .Hide() method. The code is case-sensitive, so you're not calling it correctly. It needs to be capitalized:
button[number].Hide();
checkbox[number].Hide();
Alternatively, you can set their .Visible property:
button[number].Visible = false;
checkbox[number].Visible = false;
Or are you having trouble with the array of controls? Names like button1, button2, etc. aren't particularly meaningful in most cases. But if your controls are indeed meant to be part of an ordered collection of controls, you can probably just create a collection on your form to represent them:
protected IList<Button> Buttons
{
get
{
return new List<Button>
{
button1, button2, button3; // etc.
};
}
}
Accessing the numeric values in the names themselves would otherwise be a job for reflection, which in many cases isn't really the direction you want to go. It's better to build a structure which meets your needs than to circumvent a structure which doesn't.
With this you can access the controls as an array:
Buttons[number].Hide();
Checkboxes[number].Hide();
You can take it a step further and combine the two, since they pair together. Something like this:
private class ControlGroup
{
public Button Button { get; set; }
public CheckBox CheckBox { get; set; }
public void Hide()
{
this.Button.Hide();
this.CheckBox.Hide();
}
}
(You can add further error checking within that class to guard against nulls, etc. Probably give the class a more meaningful name, too.)
Then your collection becomes:
protected IList<ControlGroup> ControlGroups
{
get
{
return new List<ControlGroup>
{
new ControlGroup { Button = button1, CheckBox = checkbox1 },
new ControlGroup { Button = button2, CheckBox = checkbox2 },
new ControlGroup { Button = button3, CheckBox = checkbox3 }
// etc.
};
}
}
This keeps things logically grouped together where appropriate into a smarter data structure, which makes the calling code easier:
ControlGroups[number].Hide();

Problems with BindlingList<T> and combo box

I have got the following code:
private BindingList<INoun> _nouns;
private BindingList<INoun> Nouns
{
get
{
if ( _nouns == null )
{
_nouns = new BindingList<INoun>( _model.Feature.Nouns );
_nouns.Insert( 0, new Noun( -1, "Please select..." ) );
}
return _nouns;
}
}
public interface INoun
{
int Id;
string Text;
}
The Nouns property is bound to a ComboBox that adds a default entry Please select... to the BindingList.
The issue I am having here is that the Please select... entry is unexpectedly being added to the underlying_model.Feature.Nouns collection and I do not want this to happen.
Is there anyway I can add a Please select... default item to a ComboBox without it being added to the underlying collection?
Thanks
BindingList is just a wrapper, mainly to get notifications, around your _model.Feature.Nouns which remains as the underlying List of items (that's why you have AllowEdit, AllowNew, AllowRemove on BindingList) :
If you want to work on a brand new list (though I'm not sure it's the purpose of the BindingList), try :
_nouns = new BindingList<INoun>( _model.Feature.Nouns.Select(x=>x).ToList());

C# Dynamic form (reflection) - linking controls

Sorry for the poor quality of the title. I couldn't think of a better way to phrase this.
For a project I'm currently working on with a few friends, I got myself in the situation where I have created a dynamic form (with reflection) which I now want to validate.
Example (ignore the black box, it contains old form elements which are now irrelevant and i didn't want to confuse you guys):
As you may have guessed already, it is an application for creating a mysql database.
Which is where I get to my problem(s). I want to disable checkboxes if others are checked.
For example: If I check "PrimaryKey" I want to disable the checkbox "Null".
Changing from unsigned to signed changes the numericupdown minimum and maximum etc.
But with reflection and all, I find it difficult to know exactly which checkbox to disable.
I was hoping you guys would have some suggestions.
I have been thinking about this for a while and a few thoughts have come to mind. Maybe these are better solutions than the current one.
Thought 1: I create UserControls for every datatype. Pro's: no problems with reflection and easy identifying of every control in the UserControl for validation. Con's: Copy-Pasting, Lots of UserControls, with a lot of the same controls.
Thought 2: Doing something with the description tags for every property of the classes. Creating rules in the description that allow me to link the checkboxes together. Here I'll only have to copy the rules to every class property and then it should be ok.
I had been thinking of other solutions but I failed to remember them.
I hope you guys can give me a few good pointers/suggestions.
[Edit]
Maybe my code can explain a bit more.
My code:
PropertyInfo[] properties = DataTypes.DataTypes.GetTypeFromString(modelElement.DataType.ToString()).GetType().GetProperties();
foreach (PropertyInfo prop in properties)
{
if (prop.Name != "Label" && prop.Name != "Project" && prop.Name != "Panel")
{
var value = prop.GetValue(modelElement.DataType, null);
if (value != null)
{
tableLayoutPanel1.Controls.Add(new Label { Text = prop.Name, Anchor = AnchorStyles.Left, AutoSize = true });
switch (value.GetType().ToString())
{
case "System.Int32":
NumericUpDown numericUpDown = new NumericUpDown();
numericUpDown.Text = value.ToString();
numericUpDown.Dock = DockStyle.None;
tableLayoutPanel1.Controls.Add(numericUpDown);
break;
case "System.Boolean":
CheckBox checkBox = new CheckBox();
checkBox.Dock = DockStyle.None;
// checkbox will become huge if not for these changes
checkBox.AutoSize = false;
checkBox.Size = new Size(16, 16);
if (value.Equals(true))
{
checkBox.CheckState = CheckState.Checked;
}
tableLayoutPanel1.Controls.Add(checkBox);
break;
default:
MessageBox.Show(#"The following type has not been implemented yet: " + value.GetType());
break;
}
}
}
}
Here is a mockup from my comments:
// The ViewModel is responsible for handling the actual visual layout of the form.
public class ViewModel {
// Fire this when your ViewModel changes
public event EventHandler WindowUpdated;
public Boolean IsIsNullCheckBoxVisible { get; private set; }
// This method would contain the actual logic for handling window changes.
public void CalculateFormLayout() {
Boolean someLogic = true;
// If the logic is true, set the isNullCheckbox to true
if (someLogic) {
IsIsNullCheckBoxVisible = true;
}
// Inform the UI to update
UpdateVisual();
}
// This fires the 'WindowUpdated' event.
public void UpdateVisual() {
if (WindowUpdated != null) {
WindowUpdated(this, new EventArgs());
}
}
}
public class TheUI : Form {
// Attach to the viewModel;
ViewModel myViewModel = new ViewModel();
CheckBox isNullCheckBox = new CheckBox();
public TheUI() {
this.myViewModel.WindowUpdated += myViewModel_WindowUpdated;
}
void myViewModel_WindowUpdated(object sender, EventArgs e) {
// Update the view here.
// Notie that all we do in the UI is to update the visual based on the
// results from the ViewModel;
this.isNullCheckBox.Visible = myViewModel.IsIsNullCheckBoxVisible;
}
}
The basic idea here is that you ensure that the UI does as little as possible. It's role should just be to update. Update what? That's for the ViewModel class to decide. We perform all of the updating logic in the ViewModel class, and then when the updating computations are done, we call the UpdateVisual() event, which tells the UI that it needs to represent itself. When the WindowUpdated Event occurs, the UI just responds by displaying the configuration set up by the ViewModel.
This may seem like a lot of work to set up initially, but once in place it will save you tons and tons of time down the road. Let me know if you have any questions.
Try relating the event of one checkbox to disable the other; something like this:
private void primaryKeyBox_AfterCheck(object sender, EventArgs e)
{
nullBox.Enabled = false;
}
This is a very simple example and would have to be changed a bit, but for what I think you're asking it should work. You would also have to add to an event for the boxes being unchecked. You would also need logic to only get data from certain checkboxes based on the ones that are and are not checked.
For all the other things, such as changing the numbers based on the dropdown, change them based on events as well.
For WinForms I would use data binding.
Create an object and implement INotifyPropertyChanged and work with that object.
Then, If you have an object instance aObj:
To bind the last name property to a textbox on the form do this:
Private WithEvents txtLastNameBinding As Binding
txtLastNameBinding = New Binding("Text", aObj, "LastName", True, DataSourceUpdateMode.OnValidation, "")
txtLastName.DataBindings.Add(txtLastNameBinding)
Take a look here for more info.
INotifyPropertyChanged

Problem with [DefaultValue()] inheriting ComboBox

for a project of mine I inherited a ComboBox to change its size behaviour. In addition to this i wanted, to speed up my forms' creation, to set the default DropDownStyle to ComboBoxStyle.DropDownList
To do this i used the [Default()] command overwriting the DropDownStyle property
[DefaultValue(ComboBoxStyle.DropDownList)]
public new ComboBoxStyle DropDownStyle
{
get
{
return base.DropDownStyle;
}
set
{
base.DropDownStyle = value;
}
}
Then i modified the default value in the Designer setting the DropDownStyle to ComboBoxStyle.DropDownList.
And here comes the problem...
There is a small number of InheritedComboBox which i want to have ComboBoxStyle.DropDown because they need to work with
AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Append;
AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems;
If i set it from the Designer it works fine, however, sometimes, after i rebuild the form, it throws an exception (also at design time) regarding the ComboBoxStyle. When i look to the FormName.Designer.cs file, i can find that for the specific InheritedComboBox there is no
DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown
and I have to add it manually.
This is a little boring because sometimes i just notice it at runtime, when the program throws an exception without showing the form and i cannot test every form every time i rebuild...
Do you have any idea why i get this strange behaviour?
Is there any way to fix it?
Thanks a lot for any answer!
When you set the AutoCompleteMode or AutoCompleteSource property, I believe the designer is looking to the base ComboBox and not generating the line to set the DropDownStyle, since DropDown is the default value for ComboBox.
I was able to correct this by adding an AutoCompleteMode and AutoCompleteSource property to the inherited ComboBox, but also had to add in a line to set the base DropDownStyle because of the order in which the designer sets the properties.
Try something like this and see if it works for you:
public class MyComboBox : ComboBox
{
public MyComboBox()
{
DropDownStyle = ComboBoxStyle.DropDownList;
AutoCompleteMode = AutoCompleteMode.None;
AutoCompleteSource = AutoCompleteSource.None;
}
[DefaultValue(ComboBoxStyle.DropDownList)]
public new ComboBoxStyle DropDownStyle
{
get { return base.DropDownStyle; }
set { base.DropDownStyle = value; }
}
[DefaultValue(AutoCompleteMode.None)]
public new AutoCompleteMode AutoCompleteMode
{
get { return base.AutoCompleteMode; }
set
{
if (value != AutoCompleteMode.None)
base.DropDownStyle = ComboBoxStyle.DropDown;
base.AutoCompleteMode = value;
}
}
[DefaultValue(AutoCompleteSource.None)]
public new AutoCompleteSource AutoCompleteSource
{
get { return base.AutoCompleteSource; }
set
{
if (value != AutoCompleteSource.None)
base.DropDownStyle = ComboBoxStyle.DropDown;
base.AutoCompleteSource = value;
}
}
}
Try setting this value to the property in the constructor of the inherited combobox too to the value you set with DefaultValue. This should probably fix your issue.

Testing Custom Control derived from ComboBox

I've created a control derived from ComboBox, and wish to unit test its behaviour.
However, it appears to be behaving differently in my unit test to how it behaves in the real application.
In the real application, the Combobox.DataSource property and the .Items sync up - in other words when I change the Combobox.DataSource the .Items list immediately and automatically updates to show an item for each element of the DataSource.
In my test, I construct a ComboBox, assign a datasource to it, but the .Items list doesn't get updated at all, remaining at 0 items. Thus, when I try to update the .SelectedIndex to 0 in the test to select the first item, I recieve an ArgumentOutOfRangeException.
Is this because I don't have an Application.Run in my unit test starting an event loop, or is this a bit of a red herring?
EDIT: More detail on the first test:
[SetUp]
public void SetUp()
{
mECB = new EnhancedComboBox();
mECB.FormattingEnabled = true;
mECB.Location = new System.Drawing.Point( 45, 4 );
mECB.Name = "cboFind";
mECB.Size = new System.Drawing.Size( 121, 21 );
mECB.TabIndex = 3;
mECB.AddObserver( this );
mTestItems = new List<TestItem>();
mTestItems.Add( new TestItem() { Value = "Billy" } );
mTestItems.Add( new TestItem() { Value = "Bob" } );
mTestItems.Add( new TestItem() { Value = "Blues" } );
mECB.DataSource = mTestItems;
mECB.Reset();
mObservedValue = null;
}
[Test]
public void Test01_UpdateObserver()
{
mECB.SelectedIndex = 0;
Assert.AreEqual( "Billy", mObservedValue.Value );
}
The test fails on the first line, when trying to set the SelectedIndex to 0. On debugging, this appears to be because when the .DataSource is changed, the .Items collection is not updated to reflect this. However, on debugging the real application, the .Items collection is always updated when the .DataSource changes.
Surely I don't have to actually render the ComboBox in the test, I don't even have any drawing surfaces set up to render on to! Maybe the only answer I need is "How do I make the ComboBox update in the same way as when it is drawn, in a unit test scenario where I don't actually need to draw the box?"
Since you're simply calling the constructor, a lot of functionality of the combobox will not work. For example, the items will be filled when the ComboBox is drawn on screen, on a form. This does not happen when constructing it in a unit test.
Why do you want to write a unit test on that combobox?
Can't you seperate the logic which now is in the custom control? For example put this in a controller, and test that?
Why don't you test on the DataSource property instead of the Items collection?
I'm sure that Application.Run absence cannot affects any control's behavior
I'm having the same problem with a combo box where the items are data bound. My current solution is to create a Form in the test, add the combo box to the Controls collection, and then show the form in my test. Kind of ugly. All my combo box really does is list a bunch of TimeSpan objects, sorted, and with custom formatting of the TimeSpan values. It also has special behavior on keypress events. I tried extracting all the data and logic to a separate class but couldn't figure it out. There probably is a better solution but what I'm doing seems satisfactory.
To make testing easier, I created these classes in my test code:
class TestCombo : DurationComboBox {
public void SimulateKeyUp(Keys keys) { base.OnKeyUp(new KeyEventArgs(keys)); }
public DataView DataView { get { return DataSource as DataView; } }
public IEnumerable<DataRowView> Rows() { return (DataView as IEnumerable).Cast<DataRowView>(); }
public IEnumerable<int> Minutes() { return Rows().Select(row => (int)row["Minutes"]); }
}
class Target {
public TestCombo Combo { get; private set; }
public Form Form { get; private set; }
public Target() {
Combo = new TestCombo();
Form = new Form();
Form.Controls.Add(Combo);
Form.Show();
}
}
Here is a sample test:
[TestMethod()]
public void ConstructorCreatesEmptyList() {
Target t = new Target();
Assert.AreEqual<int>(0, t.Combo.DataView.Count);
Assert.AreEqual<int>(-1, t.Combo.SelectedMinutes);
Assert.IsNull(t.Combo.SelectedItem);
}
This solve some problems if target is ComboBox or any other control:
target.CreateControl();
but I was unable to set SelectedValue it has null value, my test working with two data sources for combo box, one as data source and second is binded to selevted value. With other controls everithing working fine. In the begining I was also creating form in tests, but there is problem when form on created on our build server while tests are executed.
I did a little hack to allow this in my custom derived combobox:
public class EnhancedComboBox : ComboBox
{
[... the implementation]
public void DoRefreshItems()
{
SetItemsCore(DataSource as IList);
}
}
The SetItemsCore function instructs the base combobox to load internal items with the provided list, it's what uses internally after the datasource changes.
This function never gets called when the control is not on a form, because there are lots of checks for CurrencyManagers and BindingContexts that are failing because this components, I believe, are provided by the parent form somehow.
Anyway, in the test, you have to call mECB.DoRefreshItems() just after the mECB.DataSource = mTestItems and everything should be fine if you only depend on the SelectedIndex and the Items property. Any other behavior like databinding is probably still not functional.

Categories