Archive

Posts Tagged ‘styles’

CSS Class Equivalent in WPF

November 30, 2009 1 comment

In the small hours of this morning, I had an idea about how to create a stylesheet for FlowDocuments that behaves similar to CSS. What’s the difference between CSS classes and WPF styles? Well, CSS classes are not necessarily specific to a certain element type, and they can be combined on the styled element, while WPF Styles are specific to the element type, and each element can have exactly one Style (in addition to the default style, which fills in wherever the element style doesn’t provide a value). Also, if you want to combine Styles in WPF, you have to do it in the Style definition using BasedOn, which I find quite inflexible, and which drives one crazy if one has to support a lot of independent permutations.

So, what was the idea? The first one was that CSS classes should not be implemented as Styles, but as Triggers inside styles, which are selected whenever a certain attached property has the fitting value. So, in order to support different classes of TextBlocks, one would do this:

<Style TargetType="TextBlock" >
    <Style.Triggers>
        <Trigger Property="t:MultiStyle.Selector" Value="italic">
            <Setter Property="FontStyle" Value="Italic"/>
        </Trigger>
        <Trigger Property="t:MultiStyle.Selector" Value="red">
            <Setter Property="Foreground" Value="Red"/>
        </Trigger>
        <Trigger Property="t:MultiStyle.Selector" Value="bold">
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="FontStyle" Value="Normal"/>
        </Trigger>
    </Style.Triggers>
</Style>

 

The second idea was to use a special type for the attached property. The StyleSelector class tokenizes its String value, and returns “equal” on a comparison with another string or StyleSelector if the other object contains one of the tokens that are defined:

Public Class MultiStyle
    Inherits DependencyObject

    Public Shared ReadOnly SelectorProperty As DependencyProperty = DependencyProperty.RegisterAttached("Selector", GetType(StyleSelector), GetType(MultiStyle))
    Public Shared Function GetSelector(ByVal d As DependencyObject) As StyleSelector
        Return d.GetValue(SelectorProperty)
    End Function
    Public Shared Sub SetSelector(ByVal d As DependencyObject, ByVal value As StyleSelector)
        d.SetValue(SelectorProperty, value)
    End Sub

End Class

<System.ComponentModel.TypeConverter(GetType(StyleSelectorConverter))> _
Public Class StyleSelector
    Implements IEquatable(Of StyleSelector), IEquatable(Of String)

    Public Sub New(ByVal value As String)
        _Value = value
    End Sub

    Private _Value As String
    Public Property Value() As String
        Get
            Return _Value
        End Get
        Set(ByVal value As String)
            _Value = value
        End Set
    End Property

    Public Overrides Function Equals(ByVal obj As Object) As Boolean
        If TypeOf obj Is StyleSelector Then
            Return Equals1(obj)
        End If
        If TypeOf obj Is String Then
            Return Equals2(obj)
        End If
        Return MyBase.Equals(obj)
    End Function

    Public Function Equals1(ByVal other As StyleSelector) As Boolean Implements System.IEquatable(Of StyleSelector).Equals
        If other IsNot Nothing And _Value IsNot Nothing Then
            Dim myValue() As String = _Value.Split(" "c)
            Dim otherValue() As String = other.Value.Split(" "c)
            Dim result As Boolean = False
            Dim i As Integer = 0
            Do While i < otherValue.Length And Not result
                result = myValue.Contains(otherValue(i))
                i += 1
            Loop
            Return result
        Else
            Return MyBase.Equals(other)
        End If
    End Function

    Public Function Equals2(ByVal other As String) As Boolean Implements System.IEquatable(Of String).Equals
        If other IsNot Nothing And _Value IsNot Nothing Then
            Dim myValue() As String = _Value.Split(" "c)
            Return myValue.Contains(other)
        Else
            Return MyBase.Equals(other)
        End If
    End Function
End Class

Public Class StyleSelectorConverter
    Inherits System.ComponentModel.TypeConverter

    Public Overrides Function CanConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal sourceType As System.Type) As Boolean
        Return (sourceType Is GetType(String))
    End Function

    Public Overrides Function CanConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal destinationType As System.Type) As Boolean
        Return (destinationType Is GetType(String))
    End Function

    Public Overrides Function ConvertFrom(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object) As Object
        Dim result As StyleSelector = Nothing
        If TypeOf value Is String Then
            result = New StyleSelector(value)
        End If
        Return result
    End Function

    Public Overrides Function ConvertTo(ByVal context As System.ComponentModel.ITypeDescriptorContext, ByVal culture As System.Globalization.CultureInfo, ByVal value As Object, ByVal destinationType As System.Type) As Object
        Dim result As Object = Nothing
        If TypeOf value Is StyleSelector Then
            result = DirectCast(value, StyleSelector).Value
        End If
        Return result
    End Function
End Class

The only thing that remains to do is to implement the TypeConverter that allows to use StyleSelector as an attribute value in XAML, and to define the attached property.

Applications

Since I couldn’t go back to sleep, I tried it out immediately. You can download the VB solution here. (you will have to remove the .doc extension from the zip file; wordpress.com doesn’t allow zip files directly.)

However, when I was done, the idea didn’t seem so brilliant anymore as when I woke up. There are limitations of course. The trick works only with property setters. If you want to use triggers, they will be the same for all classes. What can you do with this what you can’t do with styles? The last thing that I came up with is that one could write a style sheet editor that creates styles with identical class definitions for different types of controls, so that you can use the same set of property values for different elements, as in CSS. However, you’d have to have this editor, otherwise it would be to much work to keep the class definitions equal to each other. And, of course, one can combine multiple classes on the same element, for what it’s worth…

PS: What happens if two classes provide different values for the same property and are both set on the same element? Because triggers are executed in the order they appear in the Style definition, the class that is defined last wins and overwrites the value of its sibling…

Advertisements
Categories: Uncategorized Tags: , , ,