I have an application with multiple usercontrols that are used within certain windows. One of these usercontrols defines whether all other usercontrols in this window should allow editing, hence setting the IsEnabled
property to False
for all CheckBox
es, ComboBox
es and Button
s. However, TextBox
es should allow to copying their text, hence should not be disabled, but only read-only.
I tried traversing the LogicalTree
, but some self-built usercontrol does not have any property to disable them, but the controls contained within this usercontrol are only buttons and textboxes. That's why I tried applying a style to all changable elements (CheckBox
, ComboBox
, Button
and TextBox
), but it won't work.
In the usercontrol's Ressources
section I definded some styles:
<Style TargetType="Control" x:Key="disabledStyle">
<Setter Property="IsEnabled" Value="False" />
</Style>
<Style TargetType="TextBox" x:Key="readOnlyStyle">
<Setter Property="IsReadOnly" Value="True" />
</Style>
And in CodeBehind, after checking the condition, I tried the following:
# windowOwner is the root window containing this usercontrol
for control in [Button, ComboBox, CheckBox]:
if self.windowOwner.Resources.Contains(control):
self.windowOwner.Resources.Remove(control)
self.windowOwner.Resources.Add(control, self.Resources['disabledStyle'])
if self.windowOwner.Resources.Contains(TextBox):
self.windowOwner.Resources.Remove(TextBox)
self.windowOwner.Resources.Add(TextBox, self.Resources['readOnlyStyle'])
But nothing happened. What am I doing wrong? Should I be doing it differently?
=EDIT 1==================================================================
I now tried the following, XAML:
<Style x:Key="disabledStyle">
<!--<Setter Property="Button.IsEnabled" Value="False" />
<Setter Property="CheckBox.IsEnabled" Value="False" />-->
<Setter Property="ComboBox.IsEnabled" Value="False" />
<Setter Property="TextBox.IsReadOnly" Value="True" />
</Style>
CodeBehind:
self.windowOwner.Style = self.Resources['disabledStyle']
Suprisingly, even though the IsEnabled
property is only set for ComboBox
, everything is disabled. And if I only set the TextBox.IsReadOnly
property nothing happens. Could someone explain this?
=EDIT 2==================================================================
I now also tried using a converter:
(XAML)
<Style TargetType="Control" x:Key="disabledStyle">
<Setter Property="IsEnabled" Value="False" />
<!--<Setter Property="Button.IsEnabled" Value="False" />
<Setter Property="CheckBox.IsEnabled" Value="False" />
<Setter Property="ComboBox.IsEnabled" Value="False" /> -->
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource typeConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
<Setter Property="TextBox.IsReadOnly" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
(Converter)
public class TypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool res = value.GetType() == typeof(TextBox);
return res;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ // Don't need any convert back
return null;
}
}
But again, everything is just disabled (or nothing happens if you use the variant commented out).
I got it working traversing the visual tree:
visited = set()
def disableControls(control):
visited.add(control)
try:
for childNumber in xrange(VisualTreeHelper.GetChildrenCount(control)):
child = VisualTreeHelper.GetChild(control, childNumber)
if hasattr(child, 'Content') and child.Content not in visited:
disableControls(child.Content)
if type(child) in [Button, ComboBox, CheckBox]:
child.IsEnabled = False
elif type(child) == TextBox:
child.IsReadOnly = True
elif child not in visited:
disableControls(child)
except:
pass
disableControls(self.windowOwner)
But I also would like to be able to later reset the changes to the original state. And that would mean I'd have to save all changes, which makes this far to complicated than it should be. I'm out of ideas.
I don't think removing the style and adding a new one will notify the control to apply the new style.
You should set the style directly on the control like:
self.MyControl.Style = self.Resources['readOnlyStyle'] as Style
Syntax might be different but I'm a c# guy.
You might not be getting the resource by using self.Resources['disabledStyle']
(It usually does this when styles are defined in the control hierarchy). it can give you null and may not notice it.
try
MyControl.Style = DirectCast(FindResource("labelStyle2"), Style)
FindResource() will give you error if it doesn't find the requested resource.
Hi please try the next:
XAML
<Window x:Class="ListViewWithCanvasPanel.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:listViewWithCanvasPanel="clr-namespace:ListViewWithCanvasPanel"
Title="MainWindow" Height="350" Width="525" x:Name="This" ResizeMode="CanResize"
listViewWithCanvasPanel:Attached.AreChildrenEnabled = "true"><!--put your content here--></Window>
Attached properties code
public class Attached
{
public static readonly DependencyProperty AreChildrenEnabledProperty = DependencyProperty.RegisterAttached("AreChildrenEnabled", typeof (bool), typeof (Attached), new PropertyMetadata(default(bool), AreChildrenEnabledPropertyChangedCallback));
private static void AreChildrenEnabledPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
var val = (bool) args.NewValue;
if (val == false)
{
var visual = dependencyObject as FrameworkElement;
if (visual == null) return;
visual.Loaded -= VisualOnLoaded;
visual.Unloaded -= VisualOnUnloaded;
}
else
{
var visual = dependencyObject as FrameworkElement;
if(visual == null) return;
visual.Loaded += VisualOnLoaded;
visual.Unloaded += VisualOnUnloaded;
}
}
private static void VisualOnUnloaded(object sender, RoutedEventArgs e)
{
var visual = sender as FrameworkElement;
if (visual == null) return;
visual.Loaded -= VisualOnLoaded;
}
private static void VisualOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var visual = sender as FrameworkElement;
if (visual == null) return;
var list = visual.GetAllVisualChildren();
Debug.WriteLine("children count on loading: {0}", list.Count);
var actionOnChildrenLoading = GetActionOnEachLoadedVisualChild(visual);
if(actionOnChildrenLoading == null) return;
list.ForEach(o =>
{
var combo = o as ComboBox;
if (combo != null)
{
combo.IsEnabled = false;
}
var button = o as Button;
if (button != null)
{
button.IsEnabled = false;
}
var textBlock = o as TextBlock;
if (textBlock != null)
{
textBlock.IsEnabled = false;
}
var cb = o as CheckBox;
if (cb != null)
{
cb.IsEnabled = false;
}
var textBox = o as TextBox;
if (textBox == null) return;
textBox.IsEnabled = true;
textBox.IsReadOnly = true;
});
}
public static readonly DependencyProperty ActionOnEachLoadedVisualChildProperty = DependencyProperty.RegisterAttached(
"ActionOnEachLoadedVisualChild", typeof (Action<DependencyObject>), typeof (Attached), new PropertyMetadata(default(Action<DependencyObject>)));
public static void SetActionOnEachLoadedVisualChild(DependencyObject element, Action<DependencyObject> value)
{
element.SetValue(ActionOnEachLoadedVisualChildProperty, value);
}
public static Action<DependencyObject> GetActionOnEachLoadedVisualChild(DependencyObject element)
{
return (Action<DependencyObject>) element.GetValue(ActionOnEachLoadedVisualChildProperty);
}
public static bool GetAreChildrenEnabled(UIElement element)
{
return (bool) element.GetValue(AreChildrenEnabledProperty);
}
public static void SetAreChildrenEnabled(UIElement element, bool value)
{
element.SetValue(AreChildrenEnabledProperty, value);
}
}
Helpers code
public static class VisualTreeHelperExtensions
{
public static T FindParent<T>(this DependencyObject child) where T : DependencyObject
{
while (true)
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
child = parentObject;
}
}
public static List<DependencyObject> GetAllVisualChildren(this DependencyObject parent)
{
var resultedList = new List<DependencyObject>();
var visualQueue = new Queue<DependencyObject>();
visualQueue.Enqueue(parent);
do
{
var depObj = visualQueue.Dequeue();
var childrenCount = VisualTreeHelper.GetChildrenCount(depObj);
for (int i = 0; i < childrenCount; i++)
{
var v = VisualTreeHelper.GetChild(depObj, i);
visualQueue.Enqueue(v);
}
resultedList.Add(depObj);
} while (visualQueue.Count > 0);
resultedList.RemoveAt(0);
return resultedList;
}
}
**Short explanation:*
Find all visual children of your root (window for example), scan them and perform action based on the child type.
Regards,
try this,
1.Add a boolean property say CanUserEdit to the usercontrol which controls what can be edited in other controls.
2.Add datatrigger in other usercontrols and bind to CanUserEdit(2 datatriggers, 1 for combobox and the other for textbox).
Define in it UserControl tag without key.this way it will affect all textboxes and comboboxes present in that usercontrol.
you will also get a centralized control.
Sample Code:-
Add CanUserEdit dependency property in every userControl.
//Replace MainUserControl with your control name
public static readonly DependencyProperty CanUserEditProperty =
DependencyProperty.Register("CanUserEdit", typeof(bool),
typeof(MainUserControl));
public bool CanUserEdit
{
get { return (bool)GetValue(CanUserEditProperty); }
set { SetValue(CanUserEditProperty, value); }
}
In UserControls that contain textboxes and comboboxes you would add following code to UserControl.Resources
<UserControl.Resources>
<Style TargetType="TextBox">
<Setter Property="IsReadOnly" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CanUserEdit}" Value="false">
<Setter Property="IsReadOnly" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="ComboBox">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CanUserEdit}" Value="false">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
this will affect all the comboboxes and textboxes within that control.
And in your mainwindow,You will bind each UserControl's CanUserEdit Property to CanUserEdit property of UserControl which has control on edit.
example(MainUserControl is the control that has textboxes and comboboxes):
<local:MainUserControl CanUserEdit="{Binding CanUserEdit,ElementName=CanUserEditControl}" />
Now all you have to do is toggle CanUserEdit property of UserControl that controls edit and all controls get affected.
I got it working in a not-so-elegant way, iterating over all controls and setting the property myself. While doing so I save the infomartion about which controls I changed to be able to reset the UI to the original state. I'm not really happy with that, but it seems to work. I would have prefered setting and un-setting some style, but I did not find a way to do so.
Here's what I ended up using, but feel free to post something better. First the disabling part:
visited = set()
def disableControls(control):
visited.add(control)
for childNumber in xrange(VisualTreeHelper.GetChildrenCount(control)):
child = VisualTreeHelper.GetChild(control, childNumber)
# save the old state
if type(child) in [Button, ComboBox, CheckBox] and child.IsEnabled:
child.IsEnabled = False
self.disabledControls.add(child)
elif type(child) == TextBox and not child.IsReadOnly:
child.IsReadOnly = True
self.disabledControls.add(child)
elif child not in visited:
disableControls(child)
disableControls(self.windowOwner)
And here's the part to "reset" the UI to it's original state:
while self.disabledControls:
child = self.disabledControls.pop()
if type(child) in [Button, ComboBox, CheckBox]:
child.IsEnabled = True
elif type(child) == TextBox:
child.IsReadOnly = False
the visited
-set is just a local variable to avoid visiting controls more than once, which strangely happens e.g. for some grids. The disabledControls
-set holds all controls that where not disabled and thus have been disabled by the code and have to be reset when the UI should reset itself to the original state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With