質問
WPF で複数のスタイルを適用するにはどうすればよいですか? FrameworkElement
?たとえば、すでにスタイルが設定されているコントロールがあります。別のスタイルもありますが、最初のスタイルを吹き飛ばすことなく、それに追加したいと思っています。スタイルには異なる TargetType があるため、一方を他方で拡張することはできません。
解決
簡単な答えは、あなたがやろうとしていることは (少なくともこのバージョンの WPF では) できないということだと思います。
つまり、特定の要素に対して適用できるスタイルは 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>
他のヒント
ビー・シュトルニッツは 良いブログ投稿 このためのマークアップ拡張機能の使用については、「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)
{
}
回避策として、コンストラクター パラメーターに、スタイル名をスペースで区切って指定する単一の文字列を指定することにしました。構文はそれほど悪くありません。
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.");
}
}
マークアップ拡張機能の出力の計算
マークアップ拡張機能の出力を計算するには、MarkupExtension の「ProvideValue」というメソッドをオーバーライドする必要があります。このメソッドから返された値は、マークアップ拡張子のターゲットに設定されます。
まず、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) as Style;
これで、ProvideValue メソッドを作成するために必要な要素がすべて揃いました。
public override object ProvideValue(IServiceProvider serviceProvider)
{
Style resultStyle = new Style();
foreach (string currentResourceKey in resourceKeys)
{
Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;
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"/>
それが役立つことを願っています。
使用 AttachedProperty
次のコードのように複数のスタイルを設定するには:
public class Css
{
public static string GetClass(DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
return (string)element.GetValue(ClassProperty);
}
public static void SetClass(DependencyObject element, string value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(ClassProperty, value);
}
public static readonly DependencyProperty ClassProperty =
DependencyProperty.RegisterAttached("Class", typeof(string), typeof(Css),
new PropertyMetadata(null, OnClassChanged));
private static void OnClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ui = d as FrameworkElement;
Style newStyle = new Style();
if (e.NewValue != null)
{
var names = e.NewValue as string;
var arr = names.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var name in arr)
{
Style style = ui.FindResource(name) as Style;
foreach (var setter in style.Setters)
{
newStyle.Setters.Add(setter);
}
foreach (var trigger in style.Triggers)
{
newStyle.Triggers.Add(trigger);
}
}
}
ui.Style = newStyle;
}
}
用途:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:style_a_class_like_css"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="325">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="Red" >
<Setter Property="Foreground" Value="Red"/>
</Style>
<Style TargetType="TextBlock" x:Key="Green" >
<Setter Property="Foreground" Value="Green"/>
</Style>
<Style TargetType="TextBlock" x:Key="Size18" >
<Setter Property="FontSize" Value="18"/>
<Setter Property="Margin" Value="6"/>
</Style>
<Style TargetType="TextBlock" x:Key="Bold" >
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Window.Resources>
<StackPanel>
<Button Content="Button" local:Css.Class="Red Bold" Width="75"/>
<Button Content="Button" local:Css.Class="Red Size18" Width="75"/>
<Button Content="Button" local:Css.Class="Green Size18 Bold" Width="75"/>
</StackPanel>
</Window>
結果:
特定のプロパティに触れていない場合は、ターゲット タイプが FrameworkElement になるスタイルのすべての基本プロパティと共通プロパティを取得できます。その後、すべての共通プロパティを再度コピーする必要なく、必要なターゲット タイプごとに特定のフレーバーを作成できます。
StyleSelector を使用してこれを項目のコレクションに適用すると、おそらく同様の結果が得られるでしょう。私はこれを、ツリー内のバインドされたオブジェクトの種類に応じて TreeViewItem で異なるスタイルを使用する際の同様の問題にアプローチするために使用しました。特定のアプローチに合わせて以下のクラスを少し変更する必要があるかもしれませんが、これで開始できると思います。
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> <TreeView.ItemContainerStyleSelector <myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}" NewStyle="{StaticResource NewItemStyle}" /> </TreeView.ItemContainerStyleSelector> </TreeView>
場合によっては、パネルをネストすることでこれに対処できることがあります。Foreground を変更する Style と FontSize を変更する Style があるとします。後者を TextBlock に適用し、その Style が最初の Style となるグリッドに配置できます。これは役立つかもしれませんし、場合によっては最も簡単な方法かもしれませんが、すべての問題を解決するわけではありません。
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;
}
単一の要素だけに独自のスタイルを適用しようとしている場合 基本スタイルへの追加として、これを行うための完全に異なる方法があります。これは、コードを読みやすく保守しやすいものとして、私見でははるかに優れています。
個々の要素ごとにパラメータを調整する必要があるのは非常に一般的です。1 つの要素で使用するためだけに辞書スタイルを定義するのは、維持したり理解したりするのが非常に面倒です。1 回限りの要素の微調整のためだけにスタイルを作成することを避けるために、ここで私自身の質問に対する私の答えを読んでください。