C# Dynamic form (reflection) - linking controls - c#

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

Related

Responsive Design UI with DockPanel Suite

I have design 1 winform to look like the picture. But I want the highlighted yellow part to be dockable with dockpanel suite reference. Is that do-able or any other suggestion of better design?
Right now the treeview is on the dockpanel and the red box part is a usercontrol placed in the same dockpanel. I tried to put the redbox as another form but I can't place it as it is in the picture. Also, this winform is need to be responsive so I put in the redbox part in a table layout panel.winform design and not familiar actually with the dockpanel suite reference. If there is a beginner tutorial that I can refer to, it would be much appreciated.
Current design:
There are two approach to your problem. First is dirty one and second elegant one. By dirty and elegant i mean way they display. Method they work are both same.
I will explain to you how to do it on empty form and you just implement that in your populated one.
First create new form.
Add 2 or more GroupBoxes to it
Add some items inside them (just to see if it works)
At the top of the each boxes add Button which will toggle visibility
Our form now looks like this and let's look of code behind it.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Test
{
public partial class TestForm : Form
{
// This is property
bool ShowFirstGroupBox
{
get
{
// We let user get our property from private variable
return _ShowFirstGroupBox;
}
set
{
// When user change this property we do something based on that
switch(value)
{
case true:
groupBox1.Size = new Size(groupBox1.Width, FirstGroupBoxDefaultHeight);
break;
case false:
groupBox1.Size = new Size(groupBox1.Width, 55);
break;
}
_ShowFirstGroupBox = value;
}
}
bool ShowSecondGroupBox
{
get
{
return _ShowSecondGroupBox;
}
set
{
switch (value)
{
case true:
groupBox2.Size = new Size(groupBox1.Width, FirstGroupBoxDefaultHeight);
break;
case false:
groupBox2.Size = new Size(groupBox1.Width, 55);
break;
}
_ShowSecondGroupBox = value;
}
}
// We store our boxes current state ( TRUE = shown, FALSE = HIDDEN )
bool _ShowFirstGroupBox = true;
bool _ShowSecondGroupBox = true;
// We store our default height for groupboxes
int FirstGroupBoxDefaultHeight;
int SecondGroupBoxDefaultHeight;
public TestForm()
{
InitializeComponent();
// Assigning default height of our groupboxes
FirstGroupBoxDefaultHeight = groupBox1.Height;
SecondGroupBoxDefaultHeight = groupBox2.Height;
}
private void button1_Click_1(object sender, EventArgs e)
{
ShowFirstGroupBox = !(_ShowFirstGroupBox); // This sets our property value to opposite of this boolean
}
private void button1_Click_1(object sender, EventArgs e)
{
ShowSecondGroupBox = !(_ShowSecondGroupBox); // This sets our property value to opposite of this boolean
}
}
}
Now when we have code like this and press button it will collapse groupbox.
NOTE: Controls under groupbox are still on place but just hidden since they are child of groupbox and everything outside of bounds is not visible to user.
This is dirty way since i would like to display it much prettier with MINUS sign on the right side of the groupbox title so i do not have button inside it. To do this you would need to create custom control which inherits groupbox, add button to it and position it in title bar and create event for it. It is easy if you have ever tried creating custom controls but if you haven't and you think dirty approach is okay with you then do not try it.

How do I clear a user control from a winform?

This is probably a basic question, but I can't find answers because the terms are generic.
I am building a WinForm aplication. Its purpose is to set up memory in a certain chip. I think the best way to organize the application is to have a user control for each chip type, derived from a generic parent class. Think of the children as "iphone," "android" and "blackberry," derived from a parent class "phone".
VS2017 Designer has a Panel where I want the control to be. On startup, I generate an object of the base class and add it to the panel. When I press a button, the old object is deleted and replaced with a new one. Each class has just one control, a label with distinctive text.
The problem is, after I press the button, I see both texts. The panel's Controls collection has just one element, but I see the text from both objects. I have tried Refresh, Update and Invalidate withe the same results.
What do I have to do to make the old text "go away" so the only thing I see is the latest object?
private ChipMemBase ChipMemControl = new ChipMemBase();
public Form1()
{
InitializeComponent();
//tbFeedback.Text = string.Format(fmtString, 0, 1, 2, 3, 4, 5);
cbChipName.SelectedIndex = 0;
tbVersion.Text = Version;
OriginalWindowColor = tbFeedback.BackColor;
ShowChipMemControl();
PrintToFeedback(Version);
}
private void ShowChipMemControl()
{
var ctl = pnlChipMem.GetChildAtPoint(new Point(5,5));
if (null != ctl)
{
if (ctl != ChipMemControl)
{
pnlChipMem.Controls.Remove(ctl);
ctl.Dispose();
pnlChipMem.Update();
Refresh();
}
}
if (null != ChipMemControl)
{
pnlChipMem.Controls.Add(ChipMemControl);
}
}
private void btnMakeChipMemory_Click(object sender, EventArgs e)
{
ChipMemControl = new ChipMemGen2();
ShowChipMemControl();
}
Screenshots before and after clicking Create
Your ShowChipMemControl gets the control at point 5,5 and checks if it's a ChipMemControl then removes it.
I'm guessing that the reason it's not getting removed is that the control at point 5,5 is not a ChipMemControl.
You can use:
pnlChipMem.Controls.Clear()
to remove all the controls
Or:
ChipMemControl cmc = pnlChipMem.Controls.OfType<ChipMemBase>().FirstOrDefault();
if (cmc != null)
{
pnlChipMem.Controls.Remove(cmc);
cmc.Dispose();
}
To only remove the first instance of ChipMemBase on your pnlChipMem panel.
Got it. The problem was from inheritance, not window behavior. Control lblDefault in the base class, carrying the inconvenient text, was still present in the child class. I had to make it Public in the base class and remove it in the child class constructor:
InitializeComponent();
Controls.Remove(lblDefault);
lblDefault.Dispose();
lblDefault = null;
The clue was this article and project:
dynamically-and-remove-a-user-control

C# All Check-box Appearance

In my WinForms Visual Studio application i have a checkbox styled as a Flat Button with this C# code:
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (TestBox.Checked == true)
{
TestBox.Image = Image.FromFile("M:\\CheckBox_52x.png");
TestBox.ImageAlign = ContentAlignment.MiddleCenter;
TestBox.FlatAppearance.BorderSize = 0;
// make all four (!) BackColors transparent!
TestBox.BackColor = System.Drawing.Color.Transparent;
TestBox.FlatAppearance.CheckedBackColor = Color.Transparent;
TestBox.FlatAppearance.MouseDownBackColor = Color.Transparent;
}
else
{
TestBox.Image = Image.FromFile("M:\\CheckBoxUncheck_52x.png");
TestBox.ImageAlign = ContentAlignment.MiddleCenter;
TestBox.FlatAppearance.BorderSize = 0;
// make all four (!) BackColors transparent!
TestBox.BackColor = System.Drawing.Color.Cyan;
TestBox.FlatAppearance.CheckedBackColor = Color.Cyan;
TestBox.FlatAppearance.MouseDownBackColor = Color.Cyan;
}
}
I was wondering, if instead of doing this to every single checkbox in my application, can i make the "UNCHECKED" version i have coded the default checkbox style for this applicatiom - eg - every time i create a new one it appears with these properties.
Please keep in mind that i am brand new to coding in C#.
If you want multiple controls to use the same eventhandler, that's easy - just use the same event handler. Change your code to something like:
private void HandleCheckBoxCheckedChanged(object sender, EventArgs e)
{
CheckBox checkBox = (CheckBox) sender;
string imageFile;
Color color;
if (checkBox.Checked == true)
{
// TODO: Use resources embedded within your app
imageFile = "M:\\CheckBox_52x.png";
color = Color.Transparent;
}
else
{
imageFile = "M:\\CheckBoxUncheck_52x.png";
color = Color.Cyan;
}
// TODO: Load each file once and reuse the bitmap, I suspect.
checkBox.Image = Image.FromFile(imageFile);
checkBox.ImageAlign = ContentAlignment.MiddleCenter;
checkBox.FlatAppearance.BorderSize = 0;
checkBox.BackColor = color;
checkBox.FlatAppearance.CheckedBackColor = color;
checkBox.FlatAppearance.MouseDownBackColor = color;
}
You can then attach the same handler to all your checkboxes.
If you have multiple classes, you could make that a public static method. At that point you may need to add the event handler in code rather than getting the designer to do it - I don't know whether the designer knows how to use static methods for event handlers. But it would just be something like:
TestBox.CheckedChanged += CheckBoxUtilities.HandleCheckBoxCheckedChanged;
That's if you really just want the same code to be used for event handlers. Other things to consider are:
Constructing a subclass of CheckBox as suggested by rakatherock. My own experience with creating custom controls in Windows Forms has not been great, but from an OO perspective it feels fine. An initial implementation could just derive from CheckBox and implicitly add an event handler which does exactly what your current code does.
If you want to find all the CheckBox controls in a form at some point, you can use the Controls property and then recurse through any control which itself a container. I won't go into the details of that now though, as it sounds like you don't really want this - unless you did it just to find all the CheckBox controls and add the same event handler to all of them.
This can be done by creating custom checkbox control.
From the question what you are trying to do is to manipulate all check boxes in the application from a single check box so here the logic goes like:
Save the All checkbox value(Checked or unchecked) in the settings
To create a settings value
Right Click Project>Properties>Settings>Give Name as someBoolValue,type as bool,Scope as User
So on CheckChanged event of this check box save the value in Settings variable that we have created just now.In CheckChangedEvent of main All Check box code goes like:
Properties.Settings.Default.someBoolValue=cbCheckBox.Checked;
Properties.Settings.Default.Save();
Now on every form load are where ever the event is suitable do like
foreach(Control c in this.Controls)
{
if(c is CheckBox)
{
c.Checked=Properties.Settings.Default.someBoolValue
}
}
Note:You can replace all the check box that i implemented with your custom controls.I have given you just an idea how to do it.

Copying a TabItem with an MVVM structure

This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.
Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.
Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.
Here is a sample of a command that I've implemented:
public viewModel()
{
allowReversing = new Command(allowReversing_Operations);
}
public Command AllowReversing
{
get { return allowReversing; }
}
private Command allowReversing;
private void allowReversing_Operations()
{
//Query for Window1
var mainWindow = Application.Current.Windows
.Cast<Window1>()
.FirstOrDefault(window => window is Window1) as Window1;
if (mainWindow.checkBox1.IsChecked == true) //Checked
{
mainWindow.checkBox9.IsEnabled = true;
mainWindow.groupBox7.IsEnabled = true;
}
else //UnChecked
{
mainWindow.checkBox9.IsEnabled = false;
mainWindow.checkBox9.IsChecked = false;
mainWindow.groupBox7.IsEnabled = false;
}
}
*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.
Now to the question:
After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads:
I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)
Thank you for your help.
Revision of Leo's answer
This is the current version of Leo's answer that I have compiling. (There were some syntax errors)
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
Here is my example of a properly-implemented dynamic TabControl in WPF.
The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.
The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.
This removes the need for "cloning" the UI or do any other weird manipulations with it.
1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.
2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.
I have this code that I made
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] {new PropertyFilterAttribute(PropertyFilterOptions.SetValues)})
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd where dpd != null select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly))
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
So it would be like
var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);

Pairing two Textboxes such that the numerical value of one must be lesser-than or equal to the other

To expand upon the title, I must create pairs of textboxes which together specify a numerical range (such as, say, 5 to 10, or -17 to -17). These textboxes must be such that the textbox which specifies the lower bound must have a lesser numerical value than the textbox which specifies the upper bound. One obvious solution come to mind: Ad hoc code in the parent form that handles the update event by checking their values. While this gets the job done, it strikes me as extremely inelegant and icky.
I suspect there must be a solid, OO-solution to this issue, but I'm not certain what that would be. How should I go about doing this?
Here's a thought - create a class called "TextBoxManager":
public class TextBoxManager
{
public List<Tuple<TextBox, TextBox>> LowerHigherPairs { get; set; }
public TextBoxManager()
{
LowerHigherPairs = new List<Tuple<TextBox, TextBox>>();
}
public void RegisterTextBoxes(TextBox lower, TextBox higher)
{
lower.Leave += TextBoxFocusLost;
higher.Leave += TextBoxFocusLost;
LowerHigherPairs.Add(new Tuple<TextBox, TextBox>(lower, higher));
}
public void TextBoxFocusLost(object sender, EventArgs e)
{
TextBox senderBox = sender as TextBox;
Tuple<TextBox, TextBox> matchingPair = LowerHigherPairs.Find(x => x.Item1 == senderBox || x.Item2 == senderBox);
if (matchingPair != null)
{
if (matchingPair.Item1 == senderBox)
{
//We know we should compare with the value in Item2.Text
}
else
{
//We know we should compare with the value in Item1.Text
}
}
}
}
In your form, declare this as a class level variable:
TextBoxManager higherLowerManager = new TextBoxManager();
Then, in your form OnLoad event, just register the pair of textboxes you want to manage:
higherLowerManager.RegisterTextBoxes(lowerEntryTextBox, higherEntryTextBox);
As you can see this class will then pair the two and subscribe them to a common event where we can work out which was which and perform the appropriate logic.
The other way to do this is to use a UserControl - this loses flexibility in terms of dynamic layout, but neatly encapsulates the entire interaction. Also, from a UI perspective, if the controls influence each other, then they should be close together anyway.

Categories