The Get of the Visible property of a control recursively looks up the tree to indicate if the control will be rendered or not.
I need a way to see what the "local" visible value of a control is regardless of what its parent controls are set to. i.e. Whether it itself was set to true or false.
I have seen this question, How to get the “real” value of the Visible property? which uses Reflection to obtain the local state, however, I have not been able to get this working for WebControls. It's also a rather dirty method of getting the value.
I have come up with the following extension method. It works by removing the control from its parent, checking the property, then putting the control back where it found it.
public static bool LocalVisible(this Control control)
{
//Get a reference to the parent
Control parent = control.Parent;
//Find where in the parent the control is.
int index = parent.Controls.IndexOf(control);
//Remove the control from the parent.
parent.Controls.Remove(control);
//Store the visible state of the control now it has no parent.
bool visible = control.Visible;
//Add the control back where it was in the parent.
parent.Controls.AddAt(index, control);
//Return the stored visible value.
return visible;
}
Is this an acceptable way of doing this? It works fine and I haven't come across any performance issues. It just seems extremely dirty and I have no doubt there could be instances in which it might fail (for example, when actually rendering).
If anyone has any thoughts on this solution or better still a nicer way of finding the value then that would be great.
After digging around a bit, I have produced the following extension method that uses reflection to retrieve the value of the Visible
flag for classes inheriting from System.Web.UI.Control
:
public static bool LocalVisible(this Control control)
{
var flags = typeof (Control)
.GetField("flags", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(control);
return ! (bool) flags.GetType()
.GetProperty("Item", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(flags, new object[] {0x10});
}
It uses reflection to find the private field flags
within the Control
object. This field is declared with an internal type, so more reflection is needed to invoke the getter of its indexer property.
The extension method has been tested on the following markup:
<asp:Panel Visible="false" runat="server">
<asp:Literal ID="litA" runat="server" />
<asp:Literal ID="litB" Visible="true" runat="server" />
<asp:Literal ID="litC" Visible="false" runat="server" />
</asp:Panel>
<asp:Literal ID="litD" runat="server" />
<asp:Literal ID="litE" Visible="true" runat="server" />
<asp:Literal ID="litF" Visible="false" runat="server" />
Test results:
litA.LocalVisible() == true
litB.LocalVisible() == true
litC.LocalVisible() == false
litD.LocalVisible() == true
litE.LocalVisible() == true
litF.LocalVisible() == false
You can expose the visiblility of controls using Property. This could solve your problem.
Please correct me if I am wrong.
In case someone tries to get Jørn Schou-Rode's code working in VB.NET, here is the code that works for me. When I simply translate his code in VB, I get an "Ambiguous match found" exception, because there are 3 flavors of the flags "Item" property.
<Extension()>
Public Function GetLocalVisible(ctl As Control) As Boolean
Dim flags As Object = GetType(Control).GetField("flags", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(ctl)
Dim infos As PropertyInfo() = flags.GetType().GetProperties(BindingFlags.Instance Or BindingFlags.NonPublic)
For Each info As PropertyInfo In infos
If info.Name = "Item" AndAlso info.PropertyType.Name = "Boolean" Then
Return Not CBool(info.GetValue(flags, New Object() {&H10}))
End If
Next
Return ctl.Visible
End Function