WPFでは、複数のスタイルをに適用するにはどうすればよいFrameworkElement
ですか? たとえば、既にスタイルを持つコントロールがあります。また、最初のスタイルを吹き飛ばさずに追加したい別のスタイルもあります。スタイルにはさまざまな TargetTypes があるため、一方を他方に拡張することはできません。
11 に答える
簡単な答えは、(少なくともこのバージョンの WPF では) やろうとしていることを実行できないということだと思います。
つまり、特定の要素に対して適用できる Style は 1 つだけです。
ただし、他の人が上で述べたように、おそらくあなたBasedOn
を助けるために使用できます. 次の緩い xaml を確認してください。その中に、2 つのスタイルを適用する要素の基本クラスに存在するプロパティを設定する基本スタイルがあることがわかります。そして、基本スタイルに基づく 2 番目のスタイルでは、別のプロパティを設定します。
したがって、ここでの考え方は...設定したいプロパティを何らかの方法で分離できる場合です...複数のスタイルを設定したい要素の継承階層に従って...回避策があるかもしれません。
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="baseStyle" TargetType="FrameworkElement">
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style TargetType="Button" BasedOn="{StaticResource baseStyle}">
<Setter Property="Content" Value="Hello World"/>
</Style>
</Page.Resources>
<Grid>
<Button Width="200" Height="50"/>
</Grid>
</Page>
お役に立てれば。
ノート:
特に注意すべきことが1つあります。TargetType
2 番目のスタイル (上記の xaml の最初のセット) を に変更するButtonBase
と、2 つのスタイルは適用されません。ただし、その制限を回避するには、以下の xaml を確認してください。基本的には、スタイルにキーを与え、そのキーで参照する必要があることを意味します。
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style x:Key="baseStyle" TargetType="FrameworkElement">
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<Style x:Key="derivedStyle" TargetType="ButtonBase" BasedOn="{StaticResource baseStyle}">
<Setter Property="Content" Value="Hello World"/>
</Style>
</Page.Resources>
<Grid>
<Button Width="200" Height="50" Style="{StaticResource derivedStyle}"/>
</Grid>
</Page>
Bea Stollnitz は、 「WPF で複数のスタイルを設定するにはどうすればよいですか?」という見出しの下に、これにマークアップ拡張機能を使用することについての良いブログ投稿をしました。
そのブログは現在死んでいるので、ここに投稿を再掲します。
WPF と Silverlight はどちらも、「BasedOn」プロパティを通じて別のスタイルからスタイルを派生させる機能を提供します。この機能により、開発者は、クラスの継承と同様の階層を使用してスタイルを整理できます。次のスタイルを検討してください。
<Style TargetType="Button" x:Key="BaseButtonStyle"> <Setter Property="Margin" Value="10" /> </Style> <Style TargetType="Button" x:Key="RedButtonStyle" BasedOn="{StaticResource BaseButtonStyle}"> <Setter Property="Foreground" Value="Red" /> </Style>
この構文では、RedButtonStyle を使用するボタンの Foreground プロパティが Red に設定され、Margin プロパティが 10 に設定されます。
この機能は WPF で長い間存在しており、Silverlight 3 で新しく追加されました。
要素に複数のスタイルを設定したい場合はどうしますか? WPF も Silverlight も、そのままではこの問題の解決策を提供しません。幸いなことに、この動作を WPF に実装する方法があります。これについては、このブログ投稿で説明します。
WPF と Silverlight は、マークアップ拡張機能を使用して、取得するロジックを必要とする値を持つプロパティを提供します。マークアップ拡張機能は、XAML でそれらを囲む中かっこの存在によって簡単に認識できます。たとえば、{Binding} マークアップ拡張機能には、データ ソースから値をフェッチし、変更が発生したときにそれを更新するロジックが含まれています。{StaticResource} マークアップ拡張機能には、キーに基づいてリソース ディクショナリから値を取得するロジックが含まれています。幸いなことに、WPF を使用すると、ユーザーは独自のカスタム マークアップ拡張機能を作成できます。この機能は Silverlight にはまだ存在しないため、このブログのソリューションは WPF にのみ適用できます。
マークアップ拡張機能を使用して 2 つのスタイルをマージする優れたソリューションを作成した人もいます。ただし、無制限の数のスタイルをマージする機能を提供するソリューションが必要でしたが、これは少しトリッキーです。
マークアップ拡張機能の作成は簡単です。最初の手順は、MarkupExtension から派生するクラスを作成し、MarkupExtensionReturnType 属性を使用して、マークアップ拡張機能から返される値を Style 型にすることを示すことです。
[MarkupExtensionReturnType(typeof(Style))] public class MultiStyleExtension : MarkupExtension { }
マークアップ拡張機能への入力の指定
マークアップ拡張機能のユーザーに、マージするスタイルを指定する簡単な方法を提供したいと考えています。ユーザーがマークアップ拡張機能への入力を指定するには、基本的に 2 つの方法があります。ユーザーは、プロパティを設定するか、パラメーターをコンストラクターに渡すことができます。このシナリオでは、ユーザーは無制限の数のスタイルを指定できる必要があるため、最初のアプローチは、「params」キーワードを使用して任意の数の文字列を取るコンストラクターを作成することでした。
public MultiStyleExtension(params string[] inputResourceKeys) { }
私の目標は、次のように入力を記述できるようにすることでした。
<Button Style="{local:MultiStyle BigButtonStyle, GreenButtonStyle}" … />
異なるスタイル キーをカンマで区切っていることに注意してください。残念ながら、カスタム マークアップ拡張機能は無制限の数のコンストラクター パラメーターをサポートしていないため、この方法ではコンパイル エラーが発生します。マージするスタイルの数が事前にわかっていれば、必要な数の文字列を取得するコンストラクターで同じ XAML 構文を使用できたはずです。
public MultiStyleExtension(string inputResourceKey1, string inputResourceKey2) { }
回避策として、スペースで区切られたスタイル名を指定する 1 つの文字列をコンストラクター パラメーターに指定することにしました。構文はそれほど悪くありません:
<Button Style="{local:MultiStyle BigButtonStyle GreenButtonStyle}" … />
private string[] resourceKeys; public MultiStyleExtension(string inputResourceKeys) { if (inputResourceKeys == null) { throw new ArgumentNullException("inputResourceKeys"); } this.resourceKeys = inputResourceKeys.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (this.resourceKeys.Length == 0) { throw new ArgumentException("No input resource keys specified."); } }
マークアップ拡張機能の出力を計算する
マークアップ拡張機能の出力を計算するには、「ProvideValue」という MarkupExtension のメソッドをオーバーライドする必要があります。このメソッドから返される値は、マークアップ拡張機能のターゲットに設定されます。
2 つのスタイルをマージする方法を知っている Style の拡張メソッドを作成することから始めました。このメソッドのコードは非常に単純です。
public static void Merge(this Style style1, Style style2) { if (style1 == null) { throw new ArgumentNullException("style1"); } if (style2 == null) { throw new ArgumentNullException("style2"); } 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); } // This code is only needed when using DynamicResources. foreach (object key in style2.Resources.Keys) { style1.Resources[key] = style2.Resources[key]; } }
上記のロジックでは、最初のスタイルが変更され、2 番目のスタイルのすべての情報が含まれるようになります。競合がある場合 (たとえば、両方のスタイルに同じプロパティのセッターがある場合)、2 番目のスタイルが優先されます。スタイルとトリガーをコピーするだけでなく、TargetType と BasedOn の値、および 2 番目のスタイルが持つ可能性のあるリソースも考慮したことに注意してください。マージされたスタイルの TargetType には、より派生したタイプを使用しました。2 番目のスタイルに BasedOn スタイルがある場合は、そのスタイルの階層を再帰的にマージします。リソースがある場合は、それらを最初のスタイルにコピーします。これらのリソースが {StaticResource} を使用して参照されている場合は、このマージ コードが実行される前に静的に解決されるため、移動する必要はありません。DynamicResources を使用している場合に備えて、このコードを追加しました。
上記の拡張メソッドにより、次の構文が有効になります。
style1.Merge(style2);
この構文は、ProvideValue 内に両方のスタイルのインスタンスがある場合に役立ちます。まあ、私はしません。コンストラクターから取得できるのは、これらのスタイルの文字列キーのリストだけです。コンストラクターのパラメーターで params がサポートされていれば、次の構文を使用して実際のスタイル インスタンスを取得できたはずです。
<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles) { }
しかし、それはうまくいきません。また、params の制限が存在しなくても、マークアップ拡張機能の別の制限にぶつかる可能性があります。属性構文の代わりにプロパティ要素構文を使用して静的リソースを指定する必要があり、冗長で面倒です (これについては私が説明します)。以前のブログ投稿でより良いバグ)。そして、これらの両方の制限が存在しない場合でも、名前だけを使用してスタイルのリストを記述したいと思います。それぞれの StaticResource よりも短くて読みやすいです。
解決策は、コードを使用して StaticResourceExtension を作成することです。文字列型のスタイル キーとサービス プロバイダーを指定すると、StaticResourceExtension を使用して実際のスタイル インスタンスを取得できます。構文は次のとおりです。
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider)
スタイルとして;
これで、ProvideValue メソッドを記述するために必要なすべての要素が揃いました。
public override object ProvideValue(IServiceProvider serviceProvider) { Style resultStyle = new Style(); foreach (string currentResourceKey in resourceKeys) { Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider)
スタイルとして;
if (currentStyle == null) { throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + "."); } resultStyle.Merge(currentStyle); } return resultStyle; }
MultiStyle マークアップ拡張機能の完全な使用例を次に示します。
<Window.Resources> <Style TargetType="Button" x:Key="SmallButtonStyle"> <Setter Property="Width" Value="120" /> <Setter Property="Height" Value="25" /> <Setter Property="FontSize" Value="12" /> </Style> <Style TargetType="Button" x:Key="GreenButtonStyle"> <Setter Property="Foreground" Value="Green" /> </Style> <Style TargetType="Button" x:Key="BoldButtonStyle"> <Setter Property="FontWeight" Value="Bold" /> </Style> </Window.Resources> <Button Style="{local:MultiStyle SmallButtonStyle GreenButtonStyle BoldButtonStyle}" Content="Small, green, bold" />
ただし、別のものから拡張することはできます.. BasedOn プロパティを見てください。
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="3" />
</Style>
<Style x:Key="AlwaysVerticalStyle" TargetType="TextBlock"
BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="VerticalAlignment" Value="Top" />
</Style>
WPF/XAML は、この機能をネイティブに提供していませんが、必要なことを実行できる拡張性を提供します。
私たちは同じ必要性に遭遇し、独自の XAML マークアップ拡張 (「MergedStylesExtension」と呼ばれる) を作成して、他の 2 つのスタイルから新しいスタイルを作成できるようにすることになりました (必要に応じて、1 つのスタイルで複数回使用できる可能性があります)。行をさらに多くのスタイルから継承します)。
WPF/XAML のバグのため、プロパティ要素の構文を使用して使用する必要がありますが、それ以外は問題なく動作しているようです。例えば、
<Button
Content="This is an example of a button using two merged styles">
<Button.Style>
<ext:MergedStyles
BasedOn="{StaticResource FirstStyle}"
MergeStyle="{StaticResource SecondStyle}"/>
</Button.Style>
</Button>
私は最近それについてここに書きました: http://swdeveloper.wordpress.com/2009/01/03/wpf-xaml-multiple-style-inheritance-and-markup-extensions/
これは、スタイルを使用およびラップするためのヘルパークラスを作成することで可能になります。ここで説明するCompoundStyleは、その方法を示しています。複数の方法がありますが、最も簡単な方法は次のことです。
<TextBlock Text="Test"
local:CompoundStyle.StyleKeys="headerStyle,textForMessageStyle,centeredStyle"/>
お役に立てば幸いです。
特定のプロパティに触れていない場合は、ターゲットタイプがFrameworkElementになるスタイルのすべての基本プロパティと共通プロパティを取得できます。次に、これらの一般的なプロパティをすべて再度コピーすることなく、必要なターゲットタイプごとに特定のフレーバーを作成できます。
場合によっては、パネルをネストすることでこれにアプローチできます。Foreground を変更する Style があり、別の Style が FontSize を変更するとします。後者を TextBlock に適用し、Style が最初のものである Grid に配置できます。これは、すべての問題を解決するわけではありませんが、役立つ場合があり、場合によっては最も簡単な方法かもしれません。
StyleSelector を使用してアイテムのコレクションにこれを適用すると、おそらく同様のものが得られます。これを使用して、ツリー内のバインドされたオブジェクトの種類に応じて TreeViewItems で異なるスタイルを使用する際の同様の問題に取り組みました。特定のアプローチに合わせて以下のクラスを少し変更する必要があるかもしれませんが、うまくいけば、これで始めることができます
public class MyTreeStyleSelector : StyleSelector
{
public Style DefaultStyle
{
get;
set;
}
public Style NewStyle
{
get;
set;
}
public override Style SelectStyle(object item, DependencyObject container)
{
ItemsControl ctrl = ItemsControl.ItemsControlFromItemContainer(container);
//apply to only the first element in the container (new node)
if (item == ctrl.Items[0])
{
return NewStyle;
}
else
{
//otherwise use the default style
return DefaultStyle;
}
}
}
次に、これをそのまま適用します
<ツリービュー> <TreeView.ItemContainerStyleSelector <myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}" NewStyle="{StaticResource NewItemStyle}" /> </TreeView.ItemContainerStyleSelector> </ツリービュー>
SelectStyle をオーバーライドすると、以下のようにリフレクションを介して GroupBy プロパティを取得できます。
public override Style SelectStyle(object item, DependencyObject container)
{
PropertyInfo p = item.GetType().GetProperty("GroupBy", BindingFlags.NonPublic | BindingFlags.Instance);
PropertyGroupDescription propertyGroupDescription = (PropertyGroupDescription)p.GetValue(item);
if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Title" )
{
return this.TitleStyle;
}
if (propertyGroupDescription != null && propertyGroupDescription.PropertyName == "Date")
{
return this.DateStyle;
}
return null;
}