codememo

WPF에서 여러 스타일을 적용하는 방법

tipmemo 2023. 4. 18. 22:47
반응형

WPF에서 여러 스타일을 적용하는 방법

WPF에 여러 합니까?FrameworkElement예를 들어, 저는 이미 스타일이 있는 컨트롤을 가지고 있습니다.저도 첫 번째 스타일을 날리지 않고 추가하고 싶은 스타일이 있습니다.스타일은 Target Type이 다르기 때문에 한쪽을 다른 쪽과 함께 확장할 수 없습니다.

내 생각에 간단한 대답은 당신이 하려고 하는 것을 (적어도 이 버전의 WPF에서는) 할 수 없다는 것이다.

즉, 특정 요소에 대해 하나의 스타일만 적용할 수 있습니다.

앞에서처럼 '어쩌다', '어쩌다', '어쩌다', '어쩌다', '어쩌다' 쓸 요.BasedOn와드도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 TargetType="Button" BasedOn="{StaticResource baseStyle}">
            <Setter Property="Content" Value="Hello World"/>
        </Style>
    </Page.Resources>
    <Grid>
        <Button Width="200" Height="50"/>
    </Grid>
</Page>

주의:

을 사용하다 경우, 이 항목을 변경할 수 있습니다.TargetType두 스타일의 첫 세트)에서 "xaml" (xaml의 첫 번째 세트)로 합니다.ButtonBase되지 않습니다아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아.기본적으로 스타일에 키를 부여하고 해당 키로 참조해야 합니다.

<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는 "How can I set multiple styles in 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 특성은 빨간색으로, Margin 속성은 10으로 설정됩니다.

이 기능은 WPF에서 오랫동안 사용되어 왔으며 Silverlight 3에서 새롭게 추가되었습니다.

요소에 둘 이상의 스타일을 설정하려면 어떻게 해야 합니까?WPF도 Silverlight도 이 문제에 대한 해결책을 제공하지 않습니다.다행히 이 동작을 WPF에 실장하는 방법이 있습니다.이것은 이 블로그의 투고에서 설명하겠습니다.

WPF 및 Silverlight는 마크업 확장을 사용하여 취득하는 로직이 필요한 값을 속성으로 제공합니다.마크업 확장자는 XAML에 괄호가 둘러쌓여 있으면 쉽게 인식할 수 있습니다. 예를 들어, {Binding} 마크업 확장자는 데이터 원본에서 값을 가져와 변경 시 업데이트하는 논리를 포함하고 {StaticResource} 마크업 확장자는 키를 기반으로 리소스 사전에서 값을 가져오는 논리를 포함합니다.다행히 WPF를 사용하면 사용자가 자신의 커스텀 마크업 확장자를 작성할 수 있습니다.이 기능은 Silverlight에는 아직 없기 때문에 이 블로그의 솔루션은 WPF에만 적용됩니다.

다른 회사는 마크업 확장을 사용하여 두 가지 스타일을 병합하는 훌륭한 솔루션을 개발했습니다.다만, 저는 조금 더 까다로운, 무제한의 스타일을 결합할 수 있는 솔루션을 원했습니다.

마크업 확장자는 간단하게 쓸 수 있습니다.첫 번째 단계는 MarkupExtension에서 파생된 클래스를 만들고 MarkupExtensionReturnType 속성을 사용하여 마크업 확장에서 반환된 값이 Style 유형임을 나타냅니다.

[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyleExtension : MarkupExtension
{
}

마크업 확장자에 대한 입력 지정

당사의 마크업 확장자 사용자에게 병합할 스타일을 지정할 수 있는 간단한 방법을 제공하고자 합니다.사용자가 마크업 확장자에 대한 입력을 지정할 수 있는 방법은 기본적으로 두 가지가 있습니다.사용자는 속성을 설정하거나 생성자에게 매개 변수를 전달할 수 있습니다.이 시나리오에서는 사용자가 스타일을 무제한으로 지정할 수 있는 기능이 필요하기 때문에 첫 번째 접근법은 "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 메서드를 재정의해야 합니다.이 메서드에서 반환되는 값은 마크업 확장의 대상으로 설정됩니다.

두 스타일을 병합하는 방법을 아는 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];
    }
}

위의 논리에서 첫 번째 스타일은 두 번째 스타일의 모든 정보를 포함하도록 수정되었습니다.충돌이 있는 경우(예: 두 스타일에 동일한 특성에 대한 설정기가 있는 경우) 두 번째 스타일이 이깁니다.스타일 및 트리거 복사 외에도 TargetType 및 BasedOn 값뿐만 아니라 두 번째 스타일이 가질 수 있는 리소스도 고려했습니다.Marge 스타일의 Target Type은 파생된 타입을 사용했습니다.두 번째 스타일이 BasedOn 스타일인 경우 스타일의 계층을 반복적으로 병합합니다.만약 자원이 있다면, 나는 그것들을 첫 번째 스타일로 복사한다.이러한 리소스가 {StaticResource}을(를) 사용하여 참조되는 경우 이 병합 코드가 실행되기 전에 리소스가 정적으로 해결되므로 리소스를 이동할 필요가 없습니다.Dynamic Resources를 사용할 경우에 대비하여 이 코드를 추가했습니다.

상기의 확장 방식에서는, 다음의 구문이 유효하게 됩니다.

style1.Merge(style2);

이 구문은 ProvideValue 내에 두 스타일의 인스턴스가 있는 경우에 유용합니다.글쎄, 난 안 그래.컨스트럭터로부터 얻을 수 있는 것은, 그 스타일의 문자열 키 리스트 뿐입니다.생성자 매개 변수에서 매개 변수가 지원되는 경우 다음 구문을 사용하여 실제 스타일 인스턴스를 가져올 수 있습니다.

<Button Style="{local:MultiStyle {StaticResource BigButtonStyle}, {StaticResource GreenButtonStyle}}" … />
public MultiStyleExtension(params Style[] styles)
{
}

하지만 그건 효과가 없어.또한 파라미터 제한이 존재하지 않더라도 마크업 확장의 또 다른 제한에 직면하게 될 것입니다.그 경우, 정적 자원을 지정하기 위해서 속성 구문 대신 속성 요소 구문을 사용해야 합니다.이것은 상세하고 번거로운 것입니다(이 버그는 이전 블로그 투고에서 더 잘 설명하겠습니다).또한 두 가지 제한이 모두 존재하지 않더라도 이름만 사용하여 스타일 목록을 작성하는 것이 좋습니다. 각 스타일에 대한 Static Resource보다 읽기 쉽고 짧습니다.

해결책은 코드를 사용하여 Static Resource Extension을 생성하는 것입니다.유형 문자열의 스타일 키와 서비스 공급자를 지정하면 Static Resource Extension을 사용하여 실제 스타일 인스턴스를 가져올 수 있습니다.구문은 다음과 같습니다.

Style currentStyle = new StaticResourceExtension(currentResourceKey).ProvideValue(serviceProvider) as Style;

이제 Provide Value 메서드를 작성하기 위해 필요한 모든 요소를 갖추었습니다.

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 Markup Extension("Merged Styles Extension")을 만들어 다른 두 가지 스타일(필요한 경우 여러 번 사용하여 더 많은 스타일에서 상속할 수 있음)에서 새로운 스타일을 만들 수 있게 되었습니다.

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 static 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;
    }
}

사용방법: (xmlns:local="clr-class:style_a_class_like_css"를 오른쪽 네임스페이스를 가리킵니다.)

<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를 사용하여 아이템 모음에 적용하면 비슷한 기능을 얻을 수 있습니다.TreeView에서 다른 스타일을 사용할 때 비슷한 문제를 해결하기 위해 이 기능을 사용했습니다.트리의 바인딩된 개체 유형에 따른 항목입니다.특정 접근법에 적응하기 위해 아래 클래스를 약간 수정해야 할 수도 있지만, 이것으로 시작할 수 있기를 바랍니다.

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>Item Container Style Selector<myassembly:MyTreeStyleSelector DefaultStyle="{StaticResource DefaultItemStyle}"NewStyle="{Static Resource New Item Style}" /></TreeView>Item Container Style Selector ></TreeView>

패널을 중첩하여 접근할 수 있습니다.Foreground와 FontSize를 변경하는 Style이 있다고 가정하면 TextBlock에 후자를 적용하여 해당 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;
    }

기본 스타일과 더불어 하나의 요소에만 고유한 스타일을 적용하려는 경우 IMHO가 읽기 쉽고 유지 보수 가능한 코드를 훨씬 더 잘 적용할 수 있는 완전히 다른 방법이 있습니다.

개별 요소별로 매개 변수를 조정해야 하는 경우가 매우 흔합니다.한 요소에서만 사용하기 위한 사전 스타일을 정의하는 것은 유지 보수 또는 이해하기가 매우 번거롭습니다.일회성 요소 조정만을 위한 스타일을 만들지 않으려면 여기서 내 질문에 대한 답변을 읽어 보십시오.

https://stackoverflow.com/a/54497665/1402498

ChatGPT 코드와 @Jeff가 2009년에 지적한 기사를 혼합하여 아직도 해결책을 찾고 있다면, 여기 작동하며 사용하기 쉬운 솔루션이 있습니다.

c# 클래스가 필요합니다.

[ContentProperty("Styles")]
[MarkupExtensionReturnType(typeof(Style))]
public class MultiStyle : MarkupExtension
{
    public List<Style> Styles { get; set; } = new List<Style>();

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var resultStyle = new Style();

        foreach (var style in Styles)
        {
            if (style == null)
                continue;

            MergeWithStyle(resultStyle, style);
        }

        return resultStyle;
    }
    private static void MergeWithStyle(Style style, Style mergeStyle)
    {
        // Recursively merge with any Styles this Style
        // might be BasedOn.
        if (mergeStyle.BasedOn != null)
            MergeWithStyle(style, mergeStyle.BasedOn);

        // Merge the Setters...
        foreach (var setter in mergeStyle.Setters)
            style.Setters.Add(setter);

        // Merge the Triggers...
        foreach (var trigger in mergeStyle.Triggers)
            style.Triggers.Add(trigger);
    }
}

xaml의 경우:

<Style x:Key="Style1" TargetType="Button">
    <!--setters here-->
    <Setter Property="FontWeight" Value="Bold"/>
</Style>
<Style x:Key="Style2" TargetType="Button">
    <!--setters here-->
        <Setter Property="FontStyle" Value="Italic" />
    </Style>
<v:MultiStyle x:Key="MergedStyle">
    <Style BasedOn="{StaticResource Style1}"/>
    <Style BasedOn="{StaticResource Style2}"/>
</v:MultiStyle>

그런 다음 요소(이 경우 버튼)에서 사용합니다.

<Button Style="{StaticResource MergedStyle}" Content="Hello World"/>

이와 같은 새로운 스타일의 베이스로 사용할 수 있습니다(마지된 스타일을 상속받기 전에 선언해야 합니다).

<Style x:Key="DerivedStyle" TargetType="Button" BasedOn="{StaticResource MergedStyle}">
    <Setter Property="Background" Value="Red"/>
</Style>

편집: 병합된 스타일을 다른 스타일의 기본 스타일로 사용하는 방법을 추가했습니다.

언급URL : https://stackoverflow.com/questions/16096/how-to-apply-multiple-styles-in-wpf

반응형