从包含的用户控件中为特定控件设置窗口样式

11
我有一个应用程序,其中包含多个用户控件,这些用户控件在某些窗口中使用。其中一个用户控件定义了此窗口中的所有其他用户控件是否允许编辑,因此将所有 CheckBoxComboBoxButtonIsEnabled 属性设置为 False。然而,TextBox 应该允许复制它们的文本,因此不应被禁用,但只能是只读状态。

我尝试遍历 LogicalTree,但一些自建用户控件没有任何禁用属性,但是该用户控件内包含的控件只有按钮和文本框。这就是我尝试对所有可改变的元素(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>

CodeBehind:

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

令人惊讶的是,即使只为ComboBox设置了IsEnabled属性,所有内容都会被禁用。如果我只设置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)

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


我的回答有帮到你吗?还是你遇到了其他问题? - Kylo Ren
6个回答

2
你可能无法通过使用self.Resources['disabledStyle']来获取资源(通常在控件层次结构中定义样式时会出现此情况)。它可能会返回null,而且你可能没有注意到。
尝试:
MyControl.Style = DirectCast(FindResource("labelStyle2"), Style)

FindResource() 如果找不到所请求的资源,就会给你返回错误。


我使用这两种方法都可以获取到资源,所以这不是真正的问题。 - causa prima

2

嗨,请尝试以下内容:

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

}

**简短解释:

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

祝好!


我已经做过类似的事情(请查看我的第二次编辑),但我也希望能够稍后将更改重置为原始状态。这意味着我必须保存所有更改,这使它比应该复杂得多。 - causa prima
@causaprima 当“AreChildrenEnabled”附加属性被更改时,您可以将它们全部绑定到一些主状态管理器。 - Ilan
我不完全明白你的意思-我应该绑定到哪个主状态管理器?另外,我刚想到的一件事是:原本被禁用的控件即使其他控件重新启用也应该保持禁用状态。我猜这也不可能通过你的方式实现,这就是为什么我更喜欢基于某种“全局”样式的解决方案,我可以设置(或取消设置)它,而不影响原始的“IsEnabled”属性。 - causa prima

2

请尝试以下操作:
1.在控制其他控件中可以编辑的用户控件中添加一个布尔属性,称为CanUserEdit。
2.在其他用户控件中添加数据触发器,并绑定到CanUserEdit(2个数据触发器,一个用于组合框,另一个用于文本框)。

在没有关键字的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); }
    }

在包含文本框和组合框的用户控件中,您需要添加以下代码到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>

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

在您的主窗口中,您需要将每个用户控件的CanUserEdit属性绑定到具有编辑控件的用户控件的CanUserEdit属性。
例如(MainUserControl是具有文本框和组合框的控件):

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

现在,您需要切换控制编辑的UserControl的CanUserEdit属性,所有控件都会受到影响。

如果我理解你的提议正确,这意味着我需要手动为每个“TextBox”、“ComboBox”、“CheckBox”和“Button”添加一个DataTrigger吗?这将增加太多开销,当我添加新内容时需要记住这一点,还会使整个xaml变得混乱。如果我误解了你的意思,请澄清你的答案。 - causa prima
@causaprima,你不需要这样做。当你在UserControl标签中定义时没有键(样式键),它将影响该用户控件中存在的所有文本框和组合框。 - Betson Roy
好的,也许这是可以接受的。你能举个例子吗? - causa prima
@causaprima,我已经更新了答案并添加了一些代码。 - Betson Roy
这比我想象的要好,但每个用户控件仍然有很多开销/冗余。而且每次添加新的用户控件时都需要记住这一点。此外,如果行为应该更改,则样式也必须在太多地方更改。但还是谢谢你的努力,至少我从你的例子中学到了一些新东西 :) - causa prima

2
我认为仅仅移除样式并添加新的样式不会通知控件应用新样式。
你应该直接在控件上设置样式,像这样:
self.MyControl.Style = self.Resources['readOnlyStyle'] as Style

语法可能不同,但我是C#程序员。


我尝试了这个,但它没有正确工作,请看我的第一个编辑。 - causa prima

1
我已经想到了一种不那么优雅的方法,迭代所有控件并自己设置属性。在这样做的同时,我保存了关于我更改了哪些控件的信息,以便能够将 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)

这里是将UI“重置”为其原始状态的部分:

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集合只是一个本地变量,用于避免多次访问控件,这在某些网格中会奇怪地发生。 disabledControls集合包含所有未被禁用的控件,因此已被代码禁用并且必须在UI重置为原始状态时进行重置。


1
通过发布订阅实现这种场景的简单方法是。将属性状态发布到其他用户控件,并通过绑定/分配该属性到目标控件来设置控件的状态很容易。 我通过MvvmLight Messenger实现了类似的场景,甚至可以基于某些内部控件状态禁用一些功能区命令。

请您能否进一步阐述或举个例子? - causa prima

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接