从包含的用户控件设置窗口内某些控件的样式

Posted

技术标签:

【中文标题】从包含的用户控件设置窗口内某些控件的样式【英文标题】:Set style for certain controls within window from contained usercontrol 【发布时间】:2016-05-10 00:12:08 【问题描述】:

我有一个应用程序,其中包含在某些窗口中使用的多个用户控件。这些用户控件之一定义此窗口中的所有其他用户控件是否应允许编辑,因此将所有CheckBoxes、ComboBoxes 和Buttons 的IsEnabled 属性设置为False。但是,TextBoxes 应该允许复制他们的文本,因此不应该被禁用,而只能是只读的。

我尝试遍历LogicalTree,但是一些自建的usercontrol没有任何属性可以禁用它们,但是这个usercontrol中包含的控件只有按钮和文本框。这就是为什么我尝试将样式应用于所有可变元素(CheckBoxComboBoxButtonTextBox),但它不起作用。

在用户控件的Ressources 部分我定义了一些样式:

<Style TargetType="Control" x:Key="disabledStyle">
    <Setter Property="IsEnabled" Value="False" />
</Style>
<Style TargetType="TextBox" x:Key="readOnlyStyle">
    <Setter Property="IsReadOnly" Value="True" />
</Style>

在 CodeBehind 中,检查条件后,我尝试了以下操作:

# 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'])

但是什么也没发生。我究竟做错了什么?我应该采取不同的做法吗?

=编辑1============================================= =======================

我现在尝试了以下 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>

代码隐藏:

self.windowOwner.Style = self.Resources['disabledStyle']

令人惊讶的是,即使 IsEnabled 属性仅设置为 ComboBox,一切都被禁用。如果我只设置 TextBox.IsReadOnly 属性,则不会发生任何事情。有人可以解释一下吗?

=编辑2============================================= =======================

我现在也尝试使用转换器:

(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>

(转换器)

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;
    

但同样,一切都被禁用(或者如果您使用注释掉的变体,则不会发生任何事情)。

我让它遍历可视化树:

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)

但我也希望以后能够将更改重置为原始状态。这意味着我必须保存所有更改,这使得这比应有的复杂得多。我没有想法。

【问题讨论】:

我的回答对您有帮助吗?还是您遇到了其他问题? 【参考方案1】:

我认为删除样式并添加新样式不会通知控件应用新样式。

您应该直接在控件上设置样式,例如:

self.MyControl.Style = self.Resources['readOnlyStyle'] as Style

语法可能不同,但我是 c# 人。

【讨论】:

我试过这个,它不能正常工作,看我的第一次编辑。【参考方案2】:

您可能无法通过使用self.Resources['disabledStyle'] 获取资源(通常在控件层次结构中定义样式时这样做)。它可以给你 null 并且可能不会注意到它。

试试

MyControl.Style = DirectCast(FindResource("labelStyle2"), Style)

FindResource() 如果没有找到请求的资源,会报错。

【讨论】:

我确实用这两种方法获得了资源,所以这不是真正的问题。【参考方案3】:

您好,请尝试下一个:

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>

附加属性代码

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);
    

助手代码

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;
    


**简短说明:*

查找根目录的所有可视子项(例如窗口),扫描它们并根据子项类型执行操作。

问候,

【讨论】:

我已经做了类似的事情(参见我的第二次编辑),但我也希望以后能够将更改重置为原始状态。这意味着我必须保存所有更改,这使得这比应有的复杂得多。 @causaprima 当 AreChildrenEnabled 附加属性将被更改时,您可以将它们全部绑定到某个主状态管理器。 我不完全理解你的意思 - 我应该绑定到一些主要的状态管理器吗?另外,我刚刚想到的一些事情:即使重新启用其他控件,最初被禁用的控件也应该保持禁用状态。我想你的方法也不可能,这就是为什么我更喜欢基于一些“全局”样式的解决方案,我可以设置(或取消设置)并且不会影响原始IsEnabled-property。【参考方案4】:

试试这个, 1.向用户控件添加一个布尔属性CanUserEdit,该属性控制可以在其他控件中编辑的内容。 2.在其他用户控件中添加数据触发器并绑定到CanUserEdit(2个数据触发器,1个用于组合框,另一个用于文本框)。

在其中定义不带键的 UserControl 标记。这样它将影响该用户控件中存在的所有文本框和组合框。 您还将获得集中控制。

示例代码:- 在每个 userControl 中添加 CanUserEdit 依赖属性。

        //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); 
    

在包含文本框和组合框的 UserControls 中,您可以将以下代码添加到 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>

这将影响该控件中的所有组合框和文本框。

并且在您的主窗口中,您会将每个 UserControl 的 CanUserEdit 属性绑定到具有编辑控制权的 UserControl 的 CanUserEdit 属性。 示例(MainUserControl 是具有文本框和组合框的控件):

<local:MainUserControl  CanUserEdit="Binding CanUserEdit,ElementName=CanUserEditControl"  />

现在您所要做的就是切换 UserControl 的 CanUserEdit 属性,该属性控制编辑并且所有控件都会受到影响。

【讨论】:

如果我正确理解您的建议,这意味着我需要手动为每个TextBoxComboBoxCheckBoxButton 添加一个DataTrigger?这将是非常多的开销,在添加新东西时我需要记住这一点,并且还会不必要地弄乱整个 xaml。如果我错了,请澄清你的答案。 @causaprima 您不必这样做。当您在其中定义没有键(样式键)的 UserControl 标记时,它将影响该用户控件中存在的所有文本框和组合框。 好吧,也许可以接受。请举个例子好吗? @causaprima 我已经用一些代码更新了答案 这比我想象的要好,但对于每个用户控件来说仍然有很多开销/冗余。并且必须为每个新的用户控件记住这一点。此外,如果要更改行为,则必须在太多地方更改样式。但是感谢您的努力,至少我从您的示例中学到了一些新东西:)【参考方案5】:

我让它以一种不太优雅的方式工作,迭代所有控件并自己设置属性。这样做时,我保存了有关我更改了哪些控件以便能够将 UI 重置为原始状态的信息。我对此并不满意,但它似乎奏效了。我更喜欢设置和取消设置某些样式,但我没有找到这样做的方法。

这是我最终使用的内容,但请随时发布更好的内容。首先是禁用部分:

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)

这是将用户界面“重置”为原始状态的部分:

while self.disabledControls:
    child = self.disabledControls.pop()
    if type(child) in [Button, ComboBox, CheckBox]:
        child.IsEnabled = True
    elif type(child) == TextBox:
        child.IsReadOnly = False

visited-set 只是一个局部变量,用于避免多次访问控件,这很奇怪,例如对于一些网格。 disabledControls-set 包含所有未禁用的控件,因此已被代码禁用,并且在 UI 应将自身重置为原始状态时必须重置。

【讨论】:

【参考方案6】:

实现此场景的一种简单方法是通过发布者订阅者实现。将属性状态发布到其他用户控件并通过将该属性绑定/分配给目标控件来设置控件的状态很容易。 我通过 MvvmLight Messenger 实现了类似的场景,我什至根据一些内部控制状态禁用了一些 Ribbon 命令。

【讨论】:

您能否进一步详细说明/举个例子?

以上是关于从包含的用户控件设置窗口内某些控件的样式的主要内容,如果未能解决你的问题,请参考以下文章

WPF自定义控件の重写原生控件样式模板

从用户控件中导航WPF选项卡控件?

WPF 用户控件父级

WPF用户控件嵌套控件-从用户控件切换用户控件

WPF 样式不适用于已设置样式的用户控件

在 wpf 中应用样式时,用户控件内的组合框消失