代码之家  ›  专栏  ›  技术社区  ›  strager

WPF样式取决于复选框状态

  •  1
  • strager  · 技术社区  · 14 年前

    我正在创建一个设置编辑器,插件编写器可以在其中定义自己的用户界面来配置插件。如果未选中复选框,我将实现隐藏某些“高级”元素的功能。

    复选框XAML很简单:

    <CheckBox Name="isAdvanced">_Advanced</CheckBox>
    

    理想情况下(稍后将详细介绍),实现者只需向高级控件添加一个标志(在未选中“高级”复选框时应将其隐藏),如下所示:

    <Button library:MyLibraryControl.IsAdvanced="True">My Button</Button>
    

    问题在于让隐藏 IsAdvanced="True" 元素,当 isAdvanced.IsChecked == false . 我在窗口元素上具有此样式所需的行为:

    <Window.Resources>
        <Style TargetType="Button">
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding (library:MyLibraryControl.IsAdvanced), RelativeSource={RelativeSource Mode=Self}}" Value="True" />
                        <Condition Binding="{Binding IsChecked, ElementName=isAdvanced}" Value="False" />
                    </MultiDataTrigger.Conditions>
    
                    <Setter Property="UIElement.Visibility" Value="Collapsed" />
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    

    然而,这种方法存在两个问题:

    1. 它只为按钮添加功能,而没有其他功能。这个 IsAdvanced 标记可以(应该能够)添加到 任何 视觉元素。
    2. 它替换/覆盖按钮上的样式。

    有没有其他方法来产生我想要的功能?我不怕在代码后面工作,但一个优雅的XAML解决方案是理想的(因为这纯粹是一个UI更改,除了在用户的首选项中保存复选框的状态之外)。


    人们想到了一些表示先进元素的其他方法。其中包括使用动态资源和直接绑定:

    <Button Visibility="{DynamicResource IsAdvancedVisibility}">My Button</Button>
    <Button Visibility="{Binding IsChecked, RelativeSource={...}, ValueConverter={...}}">My Button</Button>
    

    使用资源字典可能会起作用,但它似乎是一个非常糟糕的解决方案,因为UI状态似乎不应该属于字典。手动绑定相当混乱,因为必须发送复选框的状态 以某种方式 对于元素,除了硬编码值之外,我看它不会变得一团糟。

    这两个备选解决方案都将语义(“这是一个高级选项”)与外观(“高级选项应该折叠”)联系起来。来自HTML世界,我知道这是一件非常糟糕的事情,我拒绝接受这些方法,除非 绝对地 必要的。

    3 回复  |  直到 11 年前
        1
  •  0
  •   Gishu    14 年前

    不如把它移到ViewModel而不是XAML中,因为在我看来这是一种行为。

    在我看来,你想要的行为-每个插件注册一堆属性(映射到UI控件)为高级。有一个全局设置可打开/关闭高级属性。发生这种情况时,更新所有插件以显示/隐藏其高级属性

    让插件编写器实现一个只包含set属性AreAdvancedControlsVisible的接口。让他们通过属性更改处理程序在UI中隐藏/显示控件。高级UI控件可以绑定到pluginVM上的ShowAdvancedControls标志,该标志从prop changed处理程序切换为on/off。 只要设置了ShowAdvanced复选框,框架就可以循环使用可用的插件并设置这个标志。

        2
  •  0
  •   Fredrik Hedblad    14 年前

    也许有很多更好的方法来解决这个问题,但是我试图解决你的解决方案中的两个问题。有这个的小样本项目可以下载 here .

    1.它只增加了按钮的功能,而没有其他功能。这个 IsAdvanced标志可以(应该能够 添加到任何视觉元素。

    添加一个附加属性(使所有子级都继承该值)到最上面的容器可以解决此问题。

    2.它替换/重写 按钮。

    Bea Stollnitz有一篇很好的关于合并样式的博客文章 here .
    它有一个名为Merge的样式扩展方法,可以使用。

    听起来很直截了当,但是下面的问题使代码更加复杂。
    一。继承附加属性时,视觉元素没有样式。必需的已加载事件。
    2。样式在使用时无法修改。需要样式的复制方法。

    因此,我们希望此样式与父容器中所有子级的活动样式合并。

    <Style x:Key="IsAdvancedStyle">
        <Style.Triggers>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding (library:MyLibraryControl.IsAdvanced), RelativeSource={RelativeSource Mode=Self}}" Value="True" />
                    <Condition Binding="{Binding IsChecked, ElementName=isAdvanced}" Value="False" />
                </MultiDataTrigger.Conditions>
                <Setter Property="Control.Visibility" Value="Collapsed" />
            </MultiDataTrigger>
        </Style.Triggers>
    </Style>
    

    如果根容器是StackPanel,那么我们添加这个。样式IsAdvancedStyle将由所有子级继承并与活动样式合并。

    <StackPanel local:StyleChildsBehavior.StyleChilds="{StaticResource IsAdvancedStyle}">
    

    stylechildsbehavio.cs样式

    public class StyleChildsBehavior
    {
        public static readonly DependencyProperty StyleChildsProperty =
            DependencyProperty.RegisterAttached("StyleChilds",
                                                typeof(Style),
                                                typeof(StyleChildsBehavior),
                                                new FrameworkPropertyMetadata(null,
                                                        FrameworkPropertyMetadataOptions.Inherits,
                                                        StyleChildsCallback));
    
        public static void SetStyleChilds(DependencyObject element, Style value)
        {
            element.SetValue(StyleChildsProperty, value);
        }
        public static Style GetStyleChilds(DependencyObject element)
        {
            return (Style)element.GetValue(StyleChildsProperty);
        }
    
        private static void StyleChildsCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.GetIsInDesignMode(d) == true)
            {
                return;
            }
            Style isAdvancedStyle = e.NewValue as Style;
            if (isAdvancedStyle != null)
            {
                FrameworkElement element = d as FrameworkElement;
                if (element != null)
                {
                    if (element.IsLoaded == false)
                    {
                        RoutedEventHandler loadedEventHandler = null;
                        loadedEventHandler = new RoutedEventHandler(delegate
                        {
                            element.Loaded -= loadedEventHandler;
                            MergeStyles(element, isAdvancedStyle);
                        });
                        element.Loaded += loadedEventHandler;
                    }
                    else
                    {
                        MergeStyles(element, isAdvancedStyle);
                    }
                }
            }
        }
        private static void MergeStyles(FrameworkElement element, Style isAdvancedStyle)
        {
            if (element != null)
            {
                Style advancedStyle = GetStyleCopy(isAdvancedStyle);
                advancedStyle.Merge(element.Style);
                element.Style = advancedStyle;
            }
        }
        private static Style GetStyleCopy(Style style)
        {
            string savedStyle = XamlWriter.Save(style);
            using (MemoryStream memoryStream = new MemoryStream(Encoding.ASCII.GetBytes(savedStyle)))
            {
                ParserContext parserContext = new ParserContext();
                parserContext.XmlnsDictionary.Add("library", "clr-namespace:HideAll;assembly=HideAll");
                return XamlReader.Load(memoryStream, parserContext) as Style;
            }
        }
    }
    

    在此之后,IsAdvancedStyle将合并到StackPanel的所有子级中,这也适用于在运行时添加的子级。

    从博客链接修改合并扩展方法。

    public static void Merge(this Style style1, Style style2)
    {
        if (style1 == null || style2 == null)
        {
            return;
        }
        if (style1.TargetType.IsAssignableFrom(style2.TargetType))
        {
            style1.TargetType = style2.TargetType;
        }
        if (style2.BasedOn != null)
        {
            Merge(style1, style2.BasedOn);
        }
        foreach (SetterBase currentSetter in style2.Setters)
        {
            style1.Setters.Add(currentSetter);
        }
        foreach (TriggerBase currentTrigger in style2.Triggers)
        {
            style1.Triggers.Add(currentTrigger);
        }
    }
    
        3
  •  0
  •   Community prosti    7 年前

    我决定把这个问题颠倒一下,结果很好。

    我没有处理样式,而是按照 Gishu . 但是,我没有将UI放在VM中(在VM中,属性将手动传播多个层),而是使用了一个名为 ShowAdvanced 通过属性继承向下传播。

    创建此属性很简单:

    public static readonly DependencyProperty ShowAdvancedProperty;
    
    ShowAdvancedProperty = DependencyProperty.RegisterAttached(
        "ShowAdvanced",
        typeof(bool),
        typeof(MyLibraryControl),
        new FrameworkPropertyMetadata(
            false,
            FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior
        )
    );
    

    复选框设置 显示高级 整个窗口上的属性。它可以将其设置在其他地方(例如网格上),但将其放置在窗口上在我看来更有意义:

    <CheckBox Grid.Column="0"
        IsChecked="{Binding (library:MyLibraryControl.ShowAdvanced), ElementName=settingsWindow}"
        Content="_Advanced" />
    

    根据 显示高级 属性变得容易:

    <Foo.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Foo.Resources>
    
    <Button Visibility="{Binding (library:MyLibraryControl.ShowAdvanced), RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanToVisibilityConverter}}">I'm Advanced</Button>
    

    抛弃样式允许插件编写器在需要时完全更改控件的布局。它们还可以显示高级控件,但如果需要,可以保持禁用状态。风格带来了很多问题,正如梅勒克所说 workarounds were messy .

    在VM中放置“高级”显示逻辑的主要问题是,现在不太可能将多个视图绑定到同一个VM,同时保持所需的灵活性。如果“高级”逻辑在VM中,则必须显示高级控件 全部的 视图或 视图;不能一个显示,另一个隐藏。这,IMO,首先打破了VM的原则。

    (感谢所有在这里发帖的人,这很有帮助!)