繁体   English   中英

WPF样式取决于复选框状态

[英]WPF style depending on checkbox state

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

XAML复选框很简单:

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

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

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

问题在于,在isAdvanced.IsChecked == false时隐藏IsAdvanced="True"元素具有isAdvanced.IsChecked == false 我在window元素上具有此样式的预期行为:

<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世界,我知道这是一件非常不好的事情,除非绝对必要,否则我拒绝提交这些方法。

将其移到ViewModel中而不是XAML中如何操作,因为在我看来这就像行为。

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

让插件编写者实现仅包含一组属性AreAdvancedControlsVisible的接口。 让他们负责通过属性更改处理程序在其UI中隐藏/显示控件。 高级UI控件可以绑定到pluginVM上的ShowAdvancedControls标志,该标志可从prop更改的处理程序打开/关闭。 只要设置了ShowAdvanced复选框,该框架就可以循环可用的插件并设置此标志。

解决这个问题可能有很多更好的方法,但是我试图解决您在解决方案中遇到的两个问题。 与此相关的小样本项目可以在这里下载。

1.它只为按钮添加了功能,没有别的。 可以(应该)将IsAdvanced标志添加到任何视觉元素。

将附加属性(使所有子代继承该值)添加到最顶部的容器可以解决此问题。

2.它替换/替代原本在按钮上的样式。

Bea Stollnitz 在这里有一篇不错的关于合并样式的博客文章。
它有一种称为Merge的样式扩展方法,可以使用。

听起来很简单,但是以下问题使代码更加复杂。
1.继承附加属性时,Visual元素没有样式。 必需的已加载事件。
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}">

StyleChildsBehavior.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);
    }
}

我决定将问题稍微解决一下,并且效果很好。

我不使用样式,而是使用Gishu建议的属性绑定。 但是,我没有将UI放置在VM中(属性将在其中手动传播多个层),而是使用了一个名为ShowAdvanced的附加属性,该属性通过属性继承向下传播。

创建此属性很简单:

public static readonly DependencyProperty ShowAdvancedProperty;

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

复选框在整个窗口上方设置了ShowAdvanced属性。 它可以将其设置在其他位置(例如在网格上),但是将其放在窗口上更符合IMO的要求:

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

根据ShowAdvanced属性更改可见性(或所需的其他任何属性)变得容易:

<Foo.Resources>
    <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Foo.Resources>

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

沟渠样式使插件编写者可以根据需要完全更改控件的布局。 它们还可以显示高级控件,但根据需要将其禁用。 样式带来了很多问题,而且正如Meleak所展示的那样, 解决方法很杂乱

我在VM中放置“高级”显示逻辑的主要问题是,现在不再可能将多个视图绑定到同一个VM,同时又保持所需的灵活性。 如果VM中存在“高级”逻辑,则必须显示所有视图的高级控件或不显示视图。 您不能为一个显示它们,而为另一个隐藏它们。 IMO违反了首先拥有VM的原则。

(感谢所有在此发布的人;它非常有帮助!)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM