What we have
We have some complex winforms control. To store its state we use some custom serialized class. Lets say we've serialized it to xml. Now we could save this xml as a file in User directory or to include it in some another file....
But...
The question is,
if user creates several such controls across his winform application (at design time), what unique identifier is better to use in order to know which of the saved configs belongs to which of these controls?
So this identifier should:
Stay the same across application launches
Automatic given (or already given, like we can assume that Control.Name is always there)
Unique across application
I think one could imagine several ways of doing it and I believe there are might be some default ways of doing it.
What is better to use? Why?
This small extension method does the work:
public static class FormGetUniqueNameExtention
{
public static string GetFullName(this Control control)
{
if(control.Parent == null) return control.Name;
return control.Parent.GetFullName() + "." + control.Name;
}
}
It returns something like 'Form1._flowLayoutPanel.label1'
Usage:
Control aaa;
Dictionary<string, ControlConfigs> configs;
...
configs[aaa.GetFullName()] = uniqueAaaConfig;
I've been using a compound indentifier made of a full tree of control hierarchy. Assuming that your form name is Form1, then you have a groupbox Groupbox1 and a textbox TextBox1, the compound identifier would be Form1/Groupbox1/TextBox1.
If you'd like to follow this, here are the details:
http://netpl.blogspot.com/2007/07/context-help-made-easy-revisited.html
This is the method I've ended up creating to define a unique name that includes the full name of the form (with it's namespace) then each parent control above the control in question. So it could end up being something like:
MyCompany.Inventory.SomeForm1.SomeUserControl1.SomeGroupBox1.someTextBox1
static string GetUniqueName(Control c)
{
StringBuilder UniqueName = new StringBuilder();
UniqueName.Append(c.Name);
Form OwnerForm = c.FindForm();
//Start with the controls immediate parent;
Control Parent = c.Parent;
while (Parent != null)
{
if (Parent != OwnerForm)
{
//Insert the parent control name to the beginning of the unique name
UniqueName.Insert(0, Parent.Name + ".");
}
else
{
//Insert the form name along with it's namespace to the beginning of the unique name
UniqueName.Insert(0, OwnerForm.GetType() + ".");
}
//Advance to the next parent level.
Parent = Parent.Parent;
}
return UniqueName.ToString();
}
Related
I want to enable/disable controls in a Windows Forms application according to the user privileges.
Initially I thought of writing a method in each form class that would check the user credentials and then enable/disable its controls. But then I realized I could (maybe) create a static class method which would take the form as a parameter and do the job.
So I started writing it, presuming that sometimes I would like to enable the controls of just one or two panels, instead of the whole form. So, I need the parameters to be:
a varying number of panels and/or
a form class.
My difficulties with this task is that I'm getting an error trying to make the panels argument varying, and I have no idea how to set a parameter that could take any form class. All my form classes obviously inherits from Form generic class, but I don't know how to apply this.
Here's what I got:
public static void Enable(TableLayoutPanel[] containers = null)
{
if (MyOF.isEnabled)
{
return;
}
else
{
try
{
foreach (TableLayoutPanel table in containers)
{
foreach (Control control in table.Controls)
{
control.Enabled = false;
}
}
}
catch (NullReferenceException)
{
}
}
}
If we remember that the Form class derives from Control (indirectly, by deriving from ContainerControl which derives from ScrollableControl, which derives from Control), and the Enabled property belongs to the Control class, we can write a method that will enable any control's children (including the Form or TableLayoutPanel controls), since the Controls collection also belongs to the Control class:
public static void EnableChildren(Control control, bool enabled = true)
{
foreach (Control child in control.Controls)
{
child.Enabled = enabled;
}
}
And then if we also want to be able to use this with a collection of controls (as in your example), we can write an overload that takes a collection:
public static void EnableChildren(IEnumerable<Control> controls = null,
bool enabled = true)
{
if (controls == null) return;
foreach (var control in controls)
{
EnableChildren(control, enabled);
}
}
Now we can use this with a Form or a collection of TableLayoutPanel controls (or any control that has controls in it's Controls collection).
Examples of usage:
var myForm = new Form1();
EnableChildren(this); // 'this' is the current form
EnableChildren(myForm); // a separate instance of a form control
EnableChildren(tableLayoutPanel1, false); // A single TableLayoutPanel control
var tableLayoutPanels = new [] {tableLayoutPanel1, tableLayoutPanel2, tableLayoutPanel3};
EnableChildren(tableLayoutPanels); // An array of tableLayoutPanel controls
One of the simple ways I can think about what you are trying to do, is this. Let me get away for a sec here. I worked on projects where all form controls were built from Metadata. And meta came with licensing info. So, when control was placed where it should, it also was disabled or set read-only based on Metadata but the whole feature would be hidden if licensing info was restricting access to it. Coming back to your approach, this is not a bad approach and I see that this is can be done. And it can be done in 2 ways, (quickly from my head).
Use user controls as surface for the components you want to enable/disable. Create an interface
public interface IDisableableControl // make your fine name, no methods needed - marker interface
. . . . .
public class MyFineUserControl : UserControl, IDisableableControl
And in your static method that you're going to write pass the form, and find all controls that implement this interface and work them the way you want.
2.
Similarly, you can use property Tag, which is available on each control. With that, you can actually set your complex security object that can come from DB-stored metadata and then you evaluate this object stored in Tag to apply your configuration
Your method needs to be recursive
internal static void SetAllControls(Control parent)
{
// Do something with control, for example parent.Enabled = false
if (parent is IDisableableControl)
{
// here you use your logic, evaluate your parent you're dialing with and
// enable/disable correspondingly
parent.Enabled = false;
return;
}
foreach(var c in parent.Controls)
SetAllControls(c);
}
In real life, your TOP parent will be a form and will not need to be disabled, but it's certain children will. In fact, most of the time, once you found a UserControl which implements IDisableableControl that should be end of line, means, you don't need to go into children controls as they all sit on this parent and all will be disabled
I manage to accomplish what I was trying to do with the code below, which is pretty much a blend of all the helpful answers I got:
public static void EnableContainer(params Control[] containers)
{
if(containers.Count() == 0) { return; }
if (MyOF.isEnabled)
{
return;
}
else
{
try
{
foreach (var container in containers)
{
foreach (Control control in container.Controls)
{
control.Enabled = false;
}
}
}
catch (NullReferenceException)
{
}
}
}
public static void EnableForm<form>(form f) where form : Form
{
if (MyOF.isEnabled)
{
return;
}
else
{
foreach(Control control in f.Controls)
{
control.Enabled = false;
}
}
}
The community is welcome to suggest improvements as I am far from being a professional programmer. Thanks everyone once again.
I have a Main window with radio-buttons and a button that open the next window. The next window has big user controls which are made up of many identical smaller user controls. Now when a radio-button is selected it sends its name to the small user controls where it is used as the name of the library for each skin. (The skins are only changing the apperances of small user controls.)
Now the problem I have is that the name of the library reaches the small user control only after its been initialized, which is too late as by then the value of the string name is already considered null and no skin is loaded.
This is the code from the small user control:
public string name ="";
public bool SetSkin(string _Banananas)
{
name = _Banananas;
return true;
}
public SW1()
{
InitializeComponent();
Loaded += OnSkinChanged;
DataContext = this;
IsBlinking = false;
}
private void OnSkinChanged(object sender, RoutedEventArgs e)
{
mg.Resources.Clear();
mg.Resources.MergedDictionaries.Clear();
ResourceDictionary skin = Application.LoadComponent(new Uri("/TestingApp;component/Resources/Skins/" + name + ".xaml", UriKind.Relative)) as ResourceDictionary;
mg.Resources.MergedDictionaries.Add(skin);
e.Handled = true;
}
_Banananas is the string I get from the radio-button.
So any ideas?
I'm not totally sure what the mg object is that you are using in your OnSkinChanged method, but I assume, that you use the App object here.
The problem you are facing is that in WPF the loaded event is raised right after the user interface object is initialized and before you can call any methods on the newly created window or user control.
One "dirty" way to solve this problem is to create another constructor that receives the skin value as a parameter, like so:
public SW1(string skin)
{
InitializeComponent();
SetSkin(skin);
Loaded += OnSkinChanged;
DataContext = this;
IsBlinking = false;
}
Of course, you would have to call this constructor (not the default one) on the code section that creates this user control.
However, there are patterns how to use dynamic themes in WPF and your code doesn't seem to follow them. Just google it or have a look at other questions on Stack Overflow, like this one: https://stackoverflow.com/a/11151024/1560623
I have a form where I have lots of textboxes and all of them are required to be filled out. In C# how do I actually if check there are group of fields having a null or whitespace?
I am familiar with string.isNullOrWhiteSpace(string here) but I don't want to do multiple if statements of that, it would result in a bad code.
I am trying to avoid something like this
if(string.isNullOrWhiteSpace(string here)
|| string.isNullOrWhiteSpace(string here)
|| string.isNullOrWhiteSpace(string here))
{
// do something
}
Are there fix for this type of bad code?
You can query the controls collection of the form (or relevant container) and filter for textboxes and further query to see if any are empty (none should really have null values). Example:
var emptyTextboxes = from tb in this.Controls.OfType<TextBox>()
where string.IsNullOrEmpty(tb.Text)
select tb;
if (emptyTextboxes.Any())
{
// one or more textboxes are empty
}
You can do effectively the same thing using the fluent syntax.
bool isIncomplete = this.Controls.OfType<TextBox>().Any(tb => string.IsNullOrEmpty(tb.Text));
if (isIncomplete)
{
// do your work
}
For this code, you should be working with at least Visual Studio 2008 / C# 3 / .NET 3.5. Your project needs to have a reference to System.Core.dll (should have one by default) and you need a using System.Linq; directive in the class file.
Based upon your comments, consider another method if you are having trouble understanding or working with the linq version. You can certainly do this in an explicit loop (the Linq code will ultimately be a loop as well). Consider
bool isIncomplete = false;
foreach (Control control in this.Controls)
{
if (control is TextBox)
{
TextBox tb = control as TextBox;
if (string.IsNullOrEmpty(tb.Text))
{
isIncomplete = true;
break;
}
}
}
if (isIncomplete)
{
}
Finally, this code is written as if all of the textboxes are in a single container. That container might be the form, a panel, etc. You will need to point to the appropriate container (eg., instead of this (the form) it might be this.SomePanel). If you are working with controls that are in multiple and perhaps nested containers, you will need to do more work to find them programmatically (recursive searching, explicit concatenation, etc.) or you might just preload the references into an array or other collection. For example
var textboxes = new [] { textbox1, textbox2, textbox3, /* etc */ };
// write query against textboxes instead of this.Controls
You said you have multiple GroupBox controls. If each GroupBox is loaded onto the form and not nested in another control, this may get you started.
var emptyTextboxes = from groupBox in this.Controls.OfType<GroupBox>()
from tb in groupBox.Controls.OfType<TextBox>()
where string.IsNullOrEmpty(tb.Text)
select tb;
That depends on what you consider "bad code." Depending on your requirements what text boxes are required to be filled out can vary. Further, even if all of the fields are required all of the time you still want to give friendly error messages letting people know which field they didn't fill out. There a variety of approaches to solving this issue depending on how you are rendering your form. Since you haven't specified any here's a very direct method for doing so.
var incompleteTextBoxes = this.Controls.OfType<TextBox>()
.Where(tb => string.IsNullOrWhiteSpace(tb.Text));
foreach (var textBox in inCompleteTextBoxes)
{
// give user feedback about which text boxes they have yet to fill out
}
Yet another solution.
This will recursively travel the whole control Tree , and Check for null or empty text in all of the textboxes.
caveat -
If you have some fancy controls not inheriting from the standard Winforms textbox - check will not be performed
bool check(Control root,List<Control> nonFilled)
{
bool result =true;
if (root is TextBox && string.isNullOrEmpty(((TextBox)root).Text) )
{
nonFilled.Add(root);
return false;
}
foreach(Control c in root.Controls)
{
result|=check(c,nonFilled)
}
return result;
}
Usage :
List<Control> emptytextboxes=new List<Control>()
bool isOK=check(form, emptytextboxes);
I've written a web user control which I want to be able to drop into the markup for either aspx pages or other web user controls.
I need my user control to be able to easily and efficiently work out if its inside another user control or an aspx page. My initial idea is to do it recursively with checks on the Parent property - continue looking up the nesting hierarchy until I find either a web form or a user control - but I'm not sure this the best way of going about this.
Can you suggest an easier way? Thanks.
Recursively check the type of your Parent until Parent.GetType() is either typeof(UserControl) or type(Page)
private bool IsAncestorTypeOf(Control c, params Type[] typesToCheck)
{
var parent = c.Parent;
if (parent == null) return false;
if (typesToCheck.Contains(parent.GetType())) return true;
return IsAncestorTypeOf(parent, typesToCheck);
}
Or the same without recursion
private bool IsAncestorTypeOf(Control c, params Type[] typesToCheck)
{
var parent = c.Parent;
while (true)
{
if (parent == null) return false;
if (typesToCheck.Contains(parent.GetType())) return true;
parent = parent.Parent;
}
}
Call it like
var isAncestorPageOrUserControl = IsAncestorTypeOf(this, typeof(Page), typeof(UserControl));
or
var isAncestorPage = IsAncestorTypeOf(this, typeof(Page));
var isAncestorUserControl = IsAncestorTypeOf(this, typeof(UserControl));
Generally, components should be unaware of their arbitrary containers, although the containers must know their components (unless it's a strong dependency situation like list items are always in a list type and you can make a strong two way relationship). However it sounds like you are reaching out into the general surroundings. You might find many cases to code for doing this and accidentally miss others.
By making the user control aware of its surroundings and the larger world you may be introducing dependencies that make your control less reusable and harder to maintain.
If something the control needs is outside of itself, you might move toward composition by forcing the developer to provide a reference to the needed thing on a property of your user control. This is the way, for example, that validation controls in ASP.NET do it, to reference an external control to validate by id.
Of course what I specified is practical only some of the time. Is there a specific reason or edge case why you need to make your user control look around itself, or can you get away with providing instructions to the developer about where the control should be used?
This should work:
C#
bool inPage = (this.NamingContainer == this.Page);
VB.NET
Dim inPage as Boolean = Me.NamingContainer is Me.Page
Edit: it seems to be not as simple as i hoped. If the usercontrol resists in a control like a GridViewRow, the NamingControl of it would be the Row and not the Page.
This takes it into account:
C#
public static bool isControlInPageOruserControl(Control uc)
{
bool inPage = uc.NamingContainer is Page;
if (inPage) {
return true;
} else if (uc.NamingContainer is UserControl) {
return false;
} else {
return isControlInPageOruserControl(uc.NamingContainer);
}
}
VB.NET:
Public Shared Function isControlInPageOruserControl(ByVal uc As Control) As Boolean
Dim inPage As Boolean = TypeOf uc.NamingContainer Is Page
If inPage Then
Return True
ElseIf TypeOf uc.NamingContainer Is UserControl Then
Return False
Else
Return isControlInPageOruserControl(uc.NamingContainer)
End If
End Function
Example code:
var div = new HtmlGenericControl("div");
div.Controls.Add(new Literal() { ID = "litSomeLit" });
var lit = (Literal)div.FindControl("litSomeLit");
Assert.IsNotNull(lit);
This code fails the assert, because lit is null. Debugging shows that div.Controls definitely contains a literal with ID of "litSomeLit." My questions are "Why?" and "Is there any way to get a control of a specific ID without doing a recursive search of div.Controls[] by hand one element at a time?"
The reason I'm doing things this way is that my actual application is not so straightforward- a method I'm writing is given a complex control with several subcontrols in a number of possible configurations. I need to access a specific control several layers down (eg, the control with ID "txtSpecificControl" might be at StartingControl.Controls[0].Controls[2].Controls[1].Controls[3]). Normally I could just do FindControl("txtSpecificControl"), but that does not seem to work when the controls were just dynamically created (as in the above example code).
Near as I can tell, there is no way to do what I'm trying to accomplish without adding the control to the page. If I had to guess, I'd say that FindControl uses the UniqueID property of the control, which generally contains the IDs of all the controls above the current one (eg OuterControlID$LowerControlId$TargetControlID). That would only get generated when the control actually gets added to the page.
Anyway, here's an implementation of recursive depth-first-search FindControl that'll work when the control is not attached to the page yet:
public static Control FindControl(Control parent, string id)
{
foreach (Control control in parent.Controls)
{
if (control.ID == id)
{
return control;
}
var childResult = FindControl(control, id);
if (childResult != null)
{
return childResult;
}
}
return null;
}
Change your code to
var div = new HtmlGenericControl("div");
Page.Controls.Add(div);
div.Controls.Add(new Literal() { ID = "litSomeLit" });
var lit = (Literal)div.FindControl("litSomeLit");
As far as i know FindControl only works when the control is in the visual tree of the page.
When you confirmed that the control was in the Controls collection, did you do that by inspecting the collection directly? FindControl() may not work in this context.
When you debug the test, is the var lit null? If so, you may have to access the member by item index instead of using the FindControl() method.