簡體   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