Clone Controls - C# (Winform) [duplicate] - c#

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
It is possible to copy all the properties of a certain control? (C# window forms)
I have to create some controls similar to a control created as design time. The created control should have same properties as a predefined control, or in other words I want to copy a control. Is there any single line of code for that purpose? or I have to set each property by a line of code?
I am doing right now is:
ListContainer_Category3 = new FlowLayoutPanel();
ListContainer_Category3.Location = ListContainer_Category1.Location;
ListContainer_Category3.BackColor = ListContainer_Category1.BackColor;
ListContainer_Category3.Size = ListContainer_Category1.Size;
ListContainer_Category3.AutoScroll = ListContainer_Category1.AutoScroll;

Generally speaking you can use reflection to copy the public properties of an object to a new instance.
When dealing with Controls however, you need to be cautious. Some properties, like WindowTarget are meant to be used only by the framework infrastructure; so you need to filter them out.
After filtering work is done, you can write the desired one-liner:
Button button2 = button1.Clone();
Here's a little code to get you started:
public static class ControlExtensions
{
public static T Clone<T>(this T controlToClone)
where T : Control
{
PropertyInfo[] controlProperties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
T instance = Activator.CreateInstance<T>();
foreach (PropertyInfo propInfo in controlProperties)
{
if (propInfo.CanWrite)
{
if(propInfo.Name != "WindowTarget")
propInfo.SetValue(instance, propInfo.GetValue(controlToClone, null), null);
}
}
return instance;
}
}
Of course, you still need to adjust naming, location etc. Also maybe handle contained controls.

Related

Get all Controls of a Type, including nested containers [duplicate]

This question already has answers here:
How to get ALL child controls of a Windows Forms form of a specific type (Button/Textbox)?
(28 answers)
Closed 2 years ago.
I have a winforms project with a multi-level container setup in place. As in, I have a set of Panels on the top layer, each of those Panels has another set of Panels nested within them, and each of those sub-panels have a set of Controls (TextBoxes, CheckBoxes, etc.) in each one.
What I want is to be able to retrieve Controls of a certain type, given only the top level Panel. For example, one set of this using the names in my project would be MainPanelTruck which has a PanelTruckCheck Panel under it, which itself has 8 CheckBox nested in it. I want to be able to retrieve those 8 CheckBox given just MainPanelTruck.
I have been trying to get OfType<>() to work but as far as I can tell, it only searches among the next level down, so when I search for CheckBox using it, I get nothing as a result, because it only looks at the sub-panels.
This would be a lot easier if the Find method in ControlCollection allowed for a predicate or condition instead of a specific string as key, because it has the searchAllChildren bool which would be very handy here.
I can think of many ways I can get the items I want here, but none of them feel particularly elegant, which is what I'm seeking here. Thanks :)
You can write a simple (extension) method that returns all nested controls of given control:
public static class ControlExtensions
{
public static IEnumerable<Control> GetAllNestedControls(this Control root)
{
var stack = new Stack<Control>();
stack.Push(root);
do
{
var control = stack.Pop();
foreach (Control child in control.Controls)
{
yield return child;
stack.Push(child);
}
}
while (stack.Count > 0);
}
}
With this in hand getting all checkboxes is easy:
panel1.GetAllNestedControls().OfType<CheckBox>()
You can also pass an optional boolean flag to search all children if you want. Check the flag before pushing child controls to stack. I would rename the method in this case to the name suggested by 00110001:
IEnumerable<Control> EnumerateControls(this Control root, bool searchAllChildren)
Usage would be
panel1.EnumerateControls(searchAllChildren: true).OfType<CheckBox>()
If I understand what you are asking, you want all controls of a certain type in a given parent container and include all sub containers
Given
public IEnumerable<T> EnumerateControls<T>(Control control) where T : Control
{
var queue = new Queue<Control>(new []{ control });
while (queue.Any())
{
var controls = queue.Dequeue().Controls;
foreach (var item in controls.OfType<T>())
yield return item;
foreach (Control subControl in controls)
queue.Enqueue(subControl);
}
}
Usage
var allCheckBoxes = EnumerateControls<CheckBox>(somePanel);

Refresh, Redraw, or Reload UserControl (UWP)

I wonder how can I reload the UserControl to it's original template from inside,
<UserControl x:Name="_UserControl">
<!-- other input controls and combos -->
<Button OnClick="Reload_UserControl"/>
</UserControl>
is there any this.reCreate() similar method?
My Answer is based on a answer posted for Windows Forms 8+ years back.
I have used the same logic from that answer and created extension method on the Root Grid to loop through and clear values of each element( Mimicking it to bring the UserControl back to its usual state)
Below is the Extension Method.
public static class Extensions
{
private static Dictionary<Type, Action<UIElement>> controldefaults = new Dictionary<Type, Action<UIElement>>()
{
{typeof(TextBox), c => ((TextBox)c).Text = String.Empty},
{typeof(CheckBox), c => ((CheckBox)c).IsChecked = false},
{typeof(ComboBox), c => ((ComboBox)c).SelectedIndex = 0},
{typeof(ListBox), c => ((ListBox)c).Items.Clear()},
{typeof(RadioButton), c => ((RadioButton)c).IsChecked = false},
};
private static void FindAndInvoke(Type type, UIElement control)
{
if (controldefaults.ContainsKey(type))
{
controldefaults[type].Invoke(control);
}
}
public static void ClearControls(this UIElementCollection controls)
{
foreach (UIElement control in controls)
{
FindAndInvoke(control.GetType(), control);
}
}
public static void ClearControls<T>(this UIElementCollection controls) where T : class
{
if (!controldefaults.ContainsKey(typeof(T))) return;
foreach (UIElement control in controls)
{
if (control.GetType().Equals(typeof(T)))
{
FindAndInvoke(typeof(T), control);
}
}
}
}
For usage, Give the RootGrid Some name. Say rootGrid. So all you need to call is
rootGrid.Children.ClearControls();
Also As mentioned in other answer, if you want to clear only specific controls, you can use
rootGrid.Children.ClearControls<TextBox>();
You can find the Extensions class on my Gist
Good Luck.
If what you mean is clearing the values entered by a user in various fields, then using MVVM makes it quite simple - you can either replace the view model with a new one and the bindings would update everything. You could also have a Clear method in the view model.
If what you mean is fixing the topology of the visual tree that might have been altered from the original - your best bet would be to replace your control with a new one.
Also worth noting - "template" is usually not a word associated with user controls. User controls define their visual tree in XAML, which is loaded every time the control is created. Templated controls use a template that is typically loaded once and applied to each control when it loads. See When to use a templated control over a UserControl? for some more potentially useful info.

How to get access to the ToolTip component of a Form by code

I am struggling with a very tricky thing. I know there must be a solution, but I need your help.
I am working on a form translation (Multi-language) class (dll). You can instantiate the class and set a language. Then, this class enumerates all the Form controls and writes an XML file with all the strings. So far, all fine.
Now the trick: If there is a ToolTip component added to the form, I want to access the control ToolTips as well, but I did not find a way to get access to the ToolTip component by code.
Any help is appreciated.
The ToolTip does not inherit Control but only Component. Therefore it is not contained in the Form.Controls-Collection. You could iterate over the Form.components.Components to get the ToolTips.
I found a solution by sudden.
Here it is:
private static void GetAllToolTips(Form frm)
{
Type typeForm = frm.GetType();
FieldInfo fieldInfo = typeForm.GetField("components", BindingFlags.Instance | BindingFlags.NonPublic);
IContainer parent = (IContainer)fieldInfo.GetValue(frm);
List<ToolTip> ToolTipList = parent.Components.OfType<ToolTip>().ToList();
if (ToolTipList.Count > 0)
{
ToolTip tt = ToolTipList[0];
foreach (Control c in frm.Controls)
{
string text = tt.GetToolTip(c);
}
}
}
Thanks for helping
ToolTip1.GetToolTip(your_control)

Preventing a propertyGrid from binding to certain properties

I am binding a properties Grid to a bunch of custom Objects that are being written by other developers. These objects are constantly being changed and updated, so they include properties that just throw NotImplemented Exceptions. Sometimes they include properties like
[Obsolete("use thingy instead to get other thing", true)]
Instead of annoying the other developers. with things that I know will be changed later. What can I do to make sure my properties Grid doesn't break on those specific properties?
Thanks for the help. the other Developers appreciate it ;)
I imagine that you are trying to bind the PropertyGrid to the objects at runtime, not in the designer. If you mean the propertygrid in the winform designer, the answer would be different, and you should look at the postFilterEvents method of ControlDesigner.
The simplest solution would be to set the BrowsableAttribute to false for the properties that you want to hide. This means that when the other developers add the ObsoleteAttribute, they should add [Browsable(false)], too. But I understand that you'd like something more "automatic". You could write a method that changes the browsable attributes of the properties of an object before passing it to the PropertyGrid. This can be done getting the TypeDescriptor for each property, then getting its BrowsableAttribute, and setting its value according to the fact that there is an ObsoleteAttribute, or that it throws an exception (this has to be done via reflection, since browsable is private). The code could be something like this:
private static void FilterProperties(object objToEdit)
{
Type t = objToEdit.GetType();
PropertyInfo[] props = t.GetProperties();
// create fooObj in order to have another instance to test for NotImplemented exceptions
// (I do not know whether your getters could have side effects that you prefer to avoid)
object fooObj = Activator.CreateInstance(t);
foreach (PropertyInfo pi in props)
{
bool filter = false;
object[] atts = pi.GetCustomAttributes(typeof(ObsoleteAttribute), true);
if (atts.Length > 0)
filter = true;
else
{
try
{
object tmp = pi.GetValue(fooObj, null);
}
catch
{
filter = true;
}
}
PropertyDescriptor pd = TypeDescriptor.GetProperties(t)[pi.Name];
BrowsableAttribute bAtt = (BrowsableAttribute)pd.Attributes[typeof(BrowsableAttribute)];
FieldInfo fi = bAtt.GetType().GetField("browsable",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
fi.SetValue(bAtt, !filter);
}
}
This should work, but it's got a limit. There must be at least a BrowsableAttribute in the class you are editing (it doesn't matter if it's set to true or false), otherwise the PropertyGrid will always be empty.

PropertyGrid alternatives

I love PropertyGrid, well, at least the concept behind it - use reflection and attributes to edit your objects without writing much UI code.
My excitement died out pretty quickly though, the default PropertyGrid shipping with WinForms flat-out sucks. Well, it's fine for editing simple objects and such, but that's as far as it goes.
It doesn't display appropriate UITypeEditors for dynamic properties which have type "Object".
As soon as your objects contain collections, you might be able to edit them with so called CollectionEditor. However, it won't fire PropertyValueChanged event. So once you need to add undo functionality, you're screwed.
And I still haven't found an elegant way to add validation for CollectionEditor.
It's also problematic to implement undo if you have multiple objects selected, because in that case PropertyValueChanged event args ChangedItem is null.
I soon found myself writing hacks to address those issues with less than agreeable results.
What would you do?
Is there an elegant solution to at least the first three issues?
Is there an alternative propertygrid? Preferably free & without PInvokes?
A lot of the PropertyGrid's elegance comes from its simplicity. Above all else, it's designed to play nice with Visual Studio, and i'd expect to see it used primarily in custom UITypeEditors and extensions, rather than in application code.
Presumably the objects you are attaching to the PropertyGrid are classes of your own design? I've found that, in order to make good use of the property grid, you have to heavily decorate your classes and members with attributes.
You may find some joy in writing your own subclasses of CollectionEditor (and other types of editors) and attaching them to class members using the [Editor] attribute - if you can attach this attribute to your dynamic properties, you can force the use of a particular editor.
The only way I can think of adding validation to CollectionEditor is to override the CreateCollectionForm() method, returning an instance of your own, custom subclass of CollectionEditor.CollectionForm. There's a chance you will be able to fire the change events from here.
Unfortunately all I can do is nod and agree with the assertion about implementing undo. You might have to resort to 'backing up' the affected objects via cloning or serialization in order to implement undo.
I've seen alternatives to the built-in property grid control, but they exist mainly to offer different visual styles.
If someone is interested - here is a workaround for the PropertyValueChanged problem that simulates a change by invoking the MemberwiseClone function of System.Object if the CollectionEditor's PropertyValueChanged had been fired ...
public class FixedCollectionEditor : CollectionEditor
{
bool modified;
public FixedCollectionEditor(Type type)
: base(type)
{ }
public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
{
value = base.EditValue(context, provider, value);
if (value != null && modified)
{
value = value.GetType()
.GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic)
.Invoke(value, new object[] { });
}
modified = false;
return value;
}
protected override CollectionForm CreateCollectionForm()
{
CollectionForm collectionForm = base.CreateCollectionForm();
foreach (Control table in collectionForm.Controls)
{
if (!(table is TableLayoutPanel)) { continue; }
foreach (Control c1 in table.Controls)
{
if (c1 is PropertyGrid)
{
PropertyGrid propertyGrid = (PropertyGrid)c1;
propertyGrid.PropertyValueChanged += new PropertyValueChangedEventHandler(GotModifiedHandler);
}
if (c1 is TableLayoutPanel)
{
foreach (Control c2 in c1.Controls)
{
if (!(c2 is Button)) { continue; }
Button button = (Button)c2;
if (button.Name == "addButton" || button.Name == "removeButton")
{
button.Click += new EventHandler(GotModifiedHandler);
if (button.ContextMenuStrip != null)
{
button.ContextMenuStrip.ItemClicked += new ToolStripItemClickedEventHandler(GotModifiedHandler);
}
}
}
}
}
}
return collectionForm;
}
void GotModifiedHandler(object sender, EventArgs e)
{
modified = true;
}
}
Visualhint sells a replacement for the property grid that may help. As I have never used it in a real project, I don't know how well it works.

Categories

Resources