Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set style for certain controls within window from contained usercontrol

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 CheckBoxes, ComboBoxes and Buttons. However, TextBoxes 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.

like image 590
causa prima Avatar asked Feb 01 '16 13:02

causa prima


5 Answers

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.

like image 105
unkreativ Avatar answered Nov 07 '22 00:11

unkreativ


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.

like image 37
Kylo Ren Avatar answered Nov 07 '22 00:11

Kylo Ren


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,

like image 27
Ilan Avatar answered Nov 06 '22 22:11

Ilan


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.

like image 35
Betson Roy Avatar answered Nov 07 '22 00:11

Betson Roy


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.

like image 1
causa prima Avatar answered Nov 07 '22 00:11

causa prima