1

プラグイン作成者がプラグインを構成するための独自のユーザーインターフェイスを定義できる設定エディターを作成しています。チェックボックスがオフの場合、特定の「高度な」要素を非表示にする機能を実装しています。

チェックボックス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>

ただし、この方法には2つの問題があります。

  1. ボタンに機能を追加するだけで、他には何も追加しません。フラグは、任意の視覚要素IsAdvancedに追加できます(追加できるはずです) 。
  2. ボタンにあるスタイルを置き換え/オーバーライドします。

必要な機能を作成する他の方法はありますか?コードビハインドで作業することを恐れていませんが、エレガントなXAMLソリューションが理想的です(これは、ユーザーの設定でチェックボックスの状態を保存することを除けば、純粋にUIの変更であるため)。


高度な要素を表す他のいくつかの方法が思い浮かびました。これには、動的リソースの使用と直接バインディングが含まれます。

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

リソースディクショナリを使用することはおそらく機能しますが、UI状態がディクショナリに属する​​べきではないように見えるため、これは本当に悪い解決策のようです。チェックボックスの状態を何らかの方法で要素に送信する必要があるため、手動でバインドすることは非常に混乱します。値をハードコーディングすることを除けば、混乱しないように見えます。

これらの代替ソリューションは両方とも、セマンティクス(「これは高度なオプションです」)を外観(「高度なオプションは折りたたむ必要があります」)に結び付けます。HTMLの世界から来た私は、これが非常に悪いことであることを知っています。絶対に必要な場合を除いて、これらのメソッドを送信することを拒否します。

4

3 に答える 3

0

この問題を解決するためのより良い方法はおそらくたくさんありますが、私はあなたがあなたの解決策で持っていた2つの問題を乗り越えようとしました。これを使用した小さなサンプルプロジェクトは、ここからダウンロードできます。

1.ボタンに機能を追加するだけで、他には何も追加しません。IsAdvancedフラグは、任意の視覚要素に追加できます(追加できるはずです)。

すべての子に値を継承させる添付プロパティを最上位のコンテナに追加すると、これを修正できます。

2.ボタンにあるスタイルを置き換え/オーバーライドします。

Bea Stollnitzには、スタイルのマージに関するすばらしいブログ記事があります
使用できるMergeと呼ばれるStyleの拡張メソッドがあります。

非常に簡単に聞こえますが、次の問題によりコードがより複雑になりました。
1.添付プロパティが継承される場合、ビジュアル要素にはスタイルがありません。必須のLoadedイベント。
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のすべての子にマージされ、これは実行時に追加される子にも適用されます。

ブログリンクからのMerge拡張メソッドを変更しました。

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);
    }
}
于 2010-11-26T19:56:21.087 に答える
0

問題を少し逆にすることにしましたが、うまくいきました。

スタイルを扱う代わりに、Gishuが提案するプロパティ バインディングを使用しました。ShowAdvancedただし、UI を VM に配置する (プロパティが複数のレイヤーを手動で伝播する) 代わりに、プロパティの継承を介して伝播するという名前の添付プロパティを使用しました。

このプロパティを作成するのは簡単です:

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>

スタイルを捨てることで、プラグイン作成者は必要に応じてコントロールのレイアウトを完全に変更できます。高度なコントロールを表示することもできますが、必要に応じて無効にしておくことができます。スタイルは多くの問題を引き起こし、メリークが示したように、回避策は厄介でした.

「高度な」表示ロジックを VM に配置することに関する私の主な問題は、必要な柔軟性を維持しながら、複数のビューを同じ VM にバインドする可能性が低くなることです。「高度な」ロジックが VM にある場合は、すべてのビューに対して高度なコントロールを表示するか、ビューをまったく表示しないようにする必要があります。ある人のためにそれらを表示し、別の人のためにそれらを隠すことはできません. これ、IMO は、そもそも VM を持つという原則を破っています。

(ここに投稿してくれたすべての人に感謝します。役に立ちました!)

于 2010-11-28T15:29:11.920 に答える
0

これは私には動作のように見えるため、XAML ではなく ViewModel に移動するのはどうですか。

あなたが望む動作は私には思えます-各プラグインは一連のプロパティ(UIコントロールへのマッピング)を高度なものとして登録します。詳細プロパティをオン/オフするグローバル設定があります。これが発生した場合は、すべてのプラグインを更新して、高度なプロパティを表示/非表示にします

プラグイン作成者に、設定のみのプロパティ AreAdvancedControlsVisible を含むインターフェイスを実装してもらいます。プロパティ変更ハンドラーを使用して、UI でコントロールを非表示/表示するようにします。高度な UI コントロールは、prop changed ハンドラーからオン/オフに切り替えられる pluginVM の ShowAdvancedControls フラグにバインドできます。フレームワークは、ShowAdvanced チェックボックスが設定されているときはいつでも、利用可能なプラグインをループしてこのフラグを設定することができます。

于 2010-11-25T08:09:25.617 に答える