I have a TabControl that contains several tabs. Each tab has one UserControl on it. I would like to check the visibility of a control x on UserControl A from UserControl B. I figured that doing x.Visible from UserControl B would be good enough. As it turns out, it was displaying false in the debugger even though I set it explicitly to true and it was never changed. Then I read on MSDN for Control.Visible that:
Even if Visible is set to true, the control might not be visible to the user if it is obscured behind other controls.
So much to my surprise, that will not work. Now I'm wondering how I can tell if the control x is visible from a different UserControl. I would like to avoid using a boolean if possible. Has anyone run into this and found a solution?
Note: It also appears that Control.IsAccessible is false in this situation.
Unfortunately the control doesn't provide anything public that will allow you to check this.
One possibility would be to set something in the controls 'Tag' property. The tag’s purpose is to associate user data with the control. So it can be anything not just a boolean.
Here is the Tag property doc
If you really want the brute force way, you can use Reflection, basically calling GetState(2):
public static bool WouldBeVisible(Control ctl)
{
// Returns true if the control would be visible if container is visible
MethodInfo mi = ctl.GetType().GetMethod("GetState", BindingFlags.Instance | BindingFlags.NonPublic);
if (mi == null) return ctl.Visible;
return (bool)(mi.Invoke(ctl, new object[] { 2 }));
}
Please try this:
bool ControlIsReallyVisible(Control C)
{
if (C.Parent == null) return C.Visible;
else return (C.Visible && ControlIsReallyVisible(C.Parent));
}
I'm using this code not only checking all the ancestors visible and also who is the root control. Checking a root is needed when the control is not added on the Mainform.
public static class StratoControlExtension
{
public static bool TruelyVisible(this Control control, Control expected_root)
{
if (control.Parent == null) { return control == expected_root && control.Visible; }
return control.Parent.TruelyVisible(expected_root) && control.Visible;
}
}
Related
Is it possible to make the whole text area of the RadComboBox clickable while having IsEditable=true and ReadOnly=True?
I would just set IsEditable = false but unfortunately I need it to be editable in order to display custom text when something is selected (I have it set so multiple things can be selected and present a list of the selected items). If I disable IsEditable then I lose the .Text attribute and can't set a custom text.
My two best bets would be:
1) somehow apply a style that makes the whole textbar clickable and not just the arrow
2) somehow apply custom text display when IsEditable is set to false.
Unfortunately I don't know how to do either so any help would be nice. Thanks
Edit: This would be ideal, except that we're using Silverlight and not ASP.net
http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/checkboxes/defaultcs.aspx
This is probably more realistic, just to somehow make the text area clickable so it opens the dropdown menu. Just like the ComboBox on the right, minus being able to type. http://demos.telerik.com/aspnet-ajax/combobox/examples/functionality/comboboxvsdropdownlist/defaultcs.aspx
I can think of several solutions, of varying elegance. Here is one that might be suitable to close your remaining gap between the Arrow-ToggleButton and the Text-Input-Area. And now that I think about it... maybe you can get rid of that rather smelly and fragile side-effect-piggybacking with the OpenDropDownOnFocus property (which will break as soon as a click does not change the focus owner).
Register a MouseLeftButtonDown click handler with the RadComboBox, you can choose to get all events, not only unhandled events. Then we can toggle the DropDown from there. But we don't want to interfere with the Arrow-ToggleButton, therefore we check from where the mouse click originated.
public class MyView : UserControl
{
public MyView()
{
InitializeComponent();
MouseButtonEventHandler handler = OnComboBoxClicked;
radComboBox.AddHandler( UIElement.MouseLeftButtonDownEvent, handler,
handledEventsToo: true );
}
private void OnComboBoxClicked( object sender, MouseButtonEventArgs args )
{
if (!args.Handled ||
!args.IsRoutedEventFromToggleButton(
togglebuttonAncestorToStopTheSearch: (UIElement) sender))
{
ToggleDropDown();
}
}
}
and extension methods for easier use:
public static class ControlExtensions
{
public static bool IsRoutedEventFromToggleButton(
this RoutedEventArgs args,
UIElement togglebuttonAncestorToStopTheSearch )
{
ToggleButton toggleButton = ((UIElement) args.OriginalSource)
.GetAncestor<ToggleButton>( togglebuttonAncestorToStopTheSearch );
return toggleButton != null;
}
public static TAncestor GetAncestor<TAncestor>(
this DependencyObject subElement,
UIElement potentialAncestorToStopTheSearch )
where TAncestor : DependencyObject
{
DependencyObject parent;
for (DependencyObject subControl = subElement; subControl != null;
subControl = parent)
{
if (subControl is TAncestor) return (TAncestor) subControl;
if (object.ReferenceEquals( subControl,
potentialAncestorToStopTheSearch )) return null;
parent = VisualTreeHelper.GetParent( subControl );
if (parent == null)
{
FrameworkElement element = subControl as FrameworkElement;
if (element != null)
{
parent = element.Parent;
}
}
}
return null;
}
}
I ended up finding a multiselectcombobox that someone else implemented here:
http://www.telerik.com/support/code-library/a-multiselect-combobox
I didn't need the whole combobox itself since we already had one implemented so I just looked at how the person was displaying a custom message while the combo box IsEditable was set to false.
After looking at that code for a while and seeing how I can make it work for me, I put
<ucControls:RadComboBox.SelectionBoxTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text,ElementName=RadCombo}" />
</DataTemplate>
</ucControls:RadComboBox.SelectionBoxTemplate>
inside the XAML of our own custom MultiSelectComboBox. (RadCombo being the name of the particular control that I wanted the Text to be linked to)
<ucControls:RadComboBox
x:Name="RadCombo"
Text=""
........
<ucControls:RadComboBox.SelectionBoxTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text,ElementName=RadCombo}" />
</DataTemplate>
</ucControls:RadComboBox.SelectionBoxTemplate>
.......
</ucControls:RadComboBox>
Using the built in SelectionBoxTemplate, this basically just added a TextBlock overlay, and the content was bound to the RadComboBox's own Text, so when we would set the Text of the RadComboBox, the TextBlock would update itself.
This was the most effective way for us to do it because it required minimal code changes, and no structure changes since we already had all the code in place for checking boxes and setting a custom text.
Hope this helps someone, best of luck!
I have a user control that is dynamically loaded in the the page load:
protected void Page_Load(object sender, EventArgs e)
{
MyControl ctl = (MyControl)LoadControl(controlPath);
ctl.ID = "mycontrol";
this.MyControlPlaceHolder.Controls.Add(ctl);
}
Front End of the Page:
<asp:PlaceHolder runat="server" ID="MyControlPlaceHolder"></asp:PlaceHolder>
I have an click event on the page that initiates and postback and calls a method where I'm trying to find the control and set some properties:
MyControl ctl = (MyControl)FindControl("mycontrol");
if (ctl != null){
ctl.MyProperty = true;
Response.Write("success");
}
else
Response.Write("fail");
This is writing fail after the postback, so it seems like I'm doing something incorrectly in finding the control. What is the best way to do this?
EDIT:
I switched it to MyControl ctl = (MyControl)this.MyControlPlaceHolder.FindControl("mycontrol");
This made it so it was finding the control, however, when the control loads after the postback, it appears as though the property is not set.
You have to use a recursive FindControl implementation because FindControl will only find direct childs. Your control is inserted in a naming container at a lower level. A generic FindControlRecurisve from MSDN:
private Control FindControlRecursive(Control rootControl, string controlID)
{
if (rootControl.ID == controlID) return rootControl;
foreach (Control controlToSearch in rootControl.Controls)
{
Control controlToReturn =
FindControlRecursive(controlToSearch, controlID);
if (controlToReturn != null) return controlToReturn;
}
return null;
}
from MSDN
Or, if you only have one specific conatiner like in your sample:
MyControl ctl = this.MyControlPlaceHolder.FindControl("mycontrol");
if (ctl != null){
ctl.MyProperty = true;
Response.Write("success");
}
else
Response.Write("fail");
ViewState enable your control
public class MyControl:Control
{
public bool MyProperty
{
get
{
return ViewState["my"] != null? (bool) ViewState["my"]: false;
}
set
{
ViewState["my"] = value;
}
}
}
Try moving the code to dynamically add the control into Init instead of load. I can't be sure but there are a lot of things that happen between Init and Load and if your control is not present and accounted for it may cause issues like this.
You added the Control in the controls collection of a placeholder.
Apart of what control is your dynamically created control, if you want to looking for your dynamically added control in that way you have to do a recursive search starting from the root (maybe the page), so, if you surf over the net, you could find good solutions for that.
Personally I prefer solutions with: generics support and expressed as extension methods, so you could use the solution everywhere. These are some usefull links
Recursive Find Control with generics
Recursive Find Control with generics by extension method
Recursive Find Control with generics by extension method and linq support/example
hope this helps
Good day!
I'm writing a .vsix to replace old controls to new ones. I have designerHost which is the current designer instance. Then I start converting like this:
foreach (OldCombo oldCmbx in OldCmbxs())
{
...
NewCombo newCmbx = designerHost.CreateComponent(NewComboType, oldcmbx.Name) as NewCmbx;
...
newCmbx.Visible = oldCmbx.Visible;
...
designerHost.DestroyComponent(oldCmbx);
}
The thing is -oldCmbx is always Visible=true, no matter how it's written in the designer.cs file. I'm always creating Visible=true newCmbx's. If I force newCmbx to be Visible=false, then designer doesn't show newCmbx after the conversion, but the visible property is still true, so Visible property is definitely not what I'm searching for. So how can I force newCmbx's to be Visible=false in designer.cs?
After digging through .NET source code I've found that ControlDesigner is shadowing Visible property of the Control, so what is going to be serialized/deserialized in InitializeComponent is far related from actual Visible property of Control.
Designer.Visible property is initialized like this:
public override void Initialize(IComponent component)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(component.GetType());
PropertyDescriptor descriptor = properties["Visible"];
if (((descriptor == null) || (descriptor.PropertyType != typeof(bool))) || !descriptor.ShouldSerializeValue(component))
{
this.Visible = true;
}
else
{
this.Visible = (bool) descriptor.GetValue(component);
}
...
}
descriptor.ShouldSerializeValue(component) for Control.Visible is always false in case of newly created control.
Designer.Visible property:
private bool Visible
{
get
{
return (bool) base.ShadowProperties["Visible"];
}
set
{
base.ShadowProperties["Visible"] = value;
}
}
In the Designer.PreFilterProperties() actual Visible property of the Control is shadowed by Visible property of the designer.
Now, when the designer is initialized(in my code that's happening when I'm creating component designerHost.CreateComponent) newCmbx.Visible is always true.
Why it is so? Because Visible property of the Control is used in painting of the control(on the designer surface as well). If I set newCmbx.Visible = false it just disappears from the design surface (but still serializes from the Visible property of the designer) - that's bad, so by design of the Control class, when Control is instantiated, it is always Visible so that it could be visible on the design surface. Any subsequent changes in Visible property influence Visible property of the designer, not Control itself (in the context of working in Designer mode).
So, what I need in order to solve that problem is Visible property of the designer.
Correct code looks like this:
foreach (OldCombo oldCmbx in OldCmbxs())
{
bool _visible = GetVisiblePropThroughReflection(designerHost.GetDesigner(oldCmbx));
...
NewCombo newCmbx = designerHost.CreateComponent(NewComboType, oldcmbx.Name) as NewCmbx;
...
SetVisiblePropThroughReflection(designerHost.GetDesigner(newCmbx), _visible);
...
designerHost.DestroyComponent(oldCmbx);
}
Currently in my program in about 10 control event handlers I have this code:
if (!mapLoaded)
return;
When I load a map through the open file dialog I set mapLoaded to true. Another way to do this would be to just disable all the controls for startup and after loading a map to enable all the controls. Unfortunately there are 30+ controls and this is just 30 lines of..
a.Enabled = true;
b.Enabled = true;
c.Enabled = true;
I can't really do a foreach loop through this.Controls either because some of the controls are menustrip items, toolstrip items, panel items, scrollbars, splitters, et cetera and that loop doesn't cover that.
Ideally there would be a way to set every control's enabled property to true in a single and simple loop but I'm not sure of how to do that. Any ideas SO?
Use data binding:
Change mapLoaded into a property that notifies observers when its value has changed...
public bool MapLoaded
{
get
{
return mapLoaded;
}
set
{
if (value != mapLoaded)
{
mapLoaded = value;
MapLoadedChanged(this, EventArgs.Empty);
}
}
}
private bool mapLoaded;
public event EventHandler MapLoadedChanged = delegate {};
// ^ or implement INotifyPropertyChanged instead
Data-bind your controls' Enabled property to MapLoaded. You can set up the data bindings either using the Windows Forms designer, or using code, e.g. right after InitializeComponent();:
a.DataBindings.Add("Enabled", this, "MapLoaded");
b.DataBindings.Add("Enabled", this, "MapLoaded");
c.DataBindings.Add("Enabled", this, "MapLoaded");
How about changing your opening strategy, have a new form that let's your user load a map, and the simply not load your main form until one has been loaded?
HEllo, I need to dynamically activate fields in a page according to the service that is going to be executed...
Let me explain:
There's a page with all the possible fields and a ListBox with all the selected services to be executed, then when the user selects which service to execute (change a car plate, for example), then I need to activate only the field(s) that the service require... (The realationship between Services and Fields are stored in a database).
public void CheckAll(int pService_Id, Control pPage)
{
foreach (Control control in pPage.Controls)
{
busExecutaServico vExecuta = new busExecutaServico();
if (vExecuta.EnableField(control.ID.ToString(), Convert.ToInt32(listBoxServices.SelectedValue)))
{
switch (control.GetType().ToString())
{
case "TextBox":
TextBox controleText = (TextBox)Page.FindControl(control.ID.ToString());
controleText.Enabled = true;
break;
Note that busExecutaServico is the class which contains the method (EnableField) for checking if the selected item matches any field on the database..
I can't seem to get the control.ID.ToString() to work properly (the ID always comes as NULL)
If anyone can help me solve this, or if there's another way (even if it's completely different from what i'm trying), it would be of great help. thanks
I like to use a recursive function for locating controls by either type or ID.
public Control FindControlRecursive(Control rootControl, string controlId)
{
if (rootControl.ID == controlId)
return rootControl;
foreach (Control control in rootControl.Controls)
{
Control foundControl = FindControlRecursive(control, controlId);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
public Control FindControlRecursive(Control rootControl, Type type)
{
if (rootControl.GetType().Equals(type))
return rootControl;
foreach (Control control in rootControl.Controls)
{
Control foundControl = FindControlRecursive(control, type);
if (foundControl != null)
{
return foundControl;
}
}
return null;
}
You can adapt these to first return a collection of controls, then process them later. Might be easier to keep track of what's happening.
I learned this technique here: http://www.west-wind.com/Weblog/posts/5127.aspx
Be aware that FindControl only searches the current naming container so Page.FindControl will only find controls that are added directly to Page. For example, if you had a repeater control that had the controls you were looking for and it was added to Page, you could find your repeater control via Page.FindControl but it wouldn't find child controls within your repeater, you'd have to recursively perform the FindControl on all container controls in the page.
This might seem a bit strange but it allows you to have controls with the same ID existing on the same page. For example, if you had 10 instances of a user control with textboxes within them called "MyName", you'd really want them to not being over-writing each other's 'MyName' fields!
Your code will come across a null for an ID unless every control has been given an ID.
Also why use:-
TextBox controleText = (TextBox)Page.FindControl(control.ID.ToString());
at all instead of:-
TextBox controleText = (TextBox)control;
and indeed since you only want to change the Enabled property consider:-
((WebControl)control).Enabled = False;
That I suspect will eliminate many case statements.
In your code you don't need to search any control - you already have it in 'control' variable. You even don't need to cast it to TextBox, just to a WebControl, just do this:
...
if (vExecuta.EnableField(control.ID.ToString(), Convert.ToInt32(listBoxServices.SelectedValue)))
((WebControl)control).Enabled = true;
P.S. control.ID is already string, so you should remove any ID.ToString() also.