Mathieu Lichtsteiner
Mathieu Lichtsteiner

Reputation: 25

Wpf Custom Control: How to implement CustomContent

I'm working on a CustomControl in WPF. I want to Implement a CardControl. I have two Contents "Front" and "Back" and I want to flip between them. But I can't define a Working Trigger in the Style to flip between the contents...

First I created a Custom Control, with DependencyProperties:

public class Card : Control {

        #region initializer

        static Card() {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Card), new FrameworkPropertyMetadata(typeof(Card)));
        }

        #endregion

        #region dependencyProperties

        public FrameworkElement CustomContent {
            get { return (FrameworkElement)GetValue(CustomContentProperty); }
            set { SetValue(CustomContentProperty, value); }
        }
        public static DependencyProperty CustomContentProperty =
            DependencyProperty.Register(nameof(CustomContent), typeof(FrameworkElement), typeof(Card), new PropertyMetadata());

        public FrameworkElement Front {
            get { return (FrameworkElement)GetValue(FrontProperty); }
            set { SetValue(FrontProperty, value); }
        }
        public static DependencyProperty FrontProperty =
            DependencyProperty.Register(nameof(Front), typeof(FrameworkElement), typeof(Card), new PropertyMetadata());

        public FrameworkElement Back {
            get { return (FrameworkElement)GetValue(BackProperty); }
            set { SetValue(BackProperty, value); }
        }
        public static DependencyProperty BackProperty =
            DependencyProperty.Register(nameof(Back), typeof(FrameworkElement), typeof(Card), new PropertyMetadata());



        public bool IsDetailed {
            get { return (bool)GetValue(IsDetailedProperty); }
            set { SetValue(IsDetailedProperty, value); }
        }
        public static readonly DependencyProperty IsDetailedProperty =
            DependencyProperty.Register(nameof(IsDetailed), typeof(bool), typeof(Card), new PropertyMetadata(false));



        public CornerRadius CornerRadius {
            get { return (CornerRadius)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }
        public static readonly DependencyProperty CornerRadiusProperty =
            DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(Card));

        #endregion

Then I defined the Style in Generic.xaml:

<Style TargetType="{x:Type local:Card}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:Card}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            CornerRadius="{TemplateBinding CornerRadius}">
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
                                          Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:Card}}, Path=CustomContent, 
                                            Converter={StaticResource Debugger}, UpdateSourceTrigger=PropertyChanged}"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource 
                                        AncestorType={x:Type local:Card}}, 
                                        Path=isDetailed, 
                                        Converter={StaticResource Debugger}}" 
                                     Value="True">
                            <Setter Property="CustomContent" 
                                    Value="{Binding RelativeSource={RelativeSource 
                                        AncestorType={x:Type local:Card}}, 
                                        Path=Back}"/>
                        </DataTrigger>

                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource 
                                        AncestorType={x:Type local:Card}}, 
                                        Path=isDetailed}" 
                                     Value="False">

                            <Setter Property="CustomContent" 
                                    Value="{Binding RelativeSource={RelativeSource 
                                        AncestorType={x:Type local:Card}}, 
                                        Path=Front}"/>
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

And I Implemented a Simple CardControl for Test-Purposes with a Button, which flips the isDetailed state:

<StackPanel>
    <CustomCharts:Card Name="TestCard">
        <CustomCharts:Card.CustomContent>
            <TextBlock Text="Hello World!"/>
        </CustomCharts:Card.CustomContent>
        <CustomCharts:Card.Front>
            <TextBlock Text="Wow vordere sache!"/>
        </CustomCharts:Card.Front>
        <CustomCharts:Card.Back>
            <TextBlock Text="Wow hintere sache!"/>
        </CustomCharts:Card.Back>
    </CustomCharts:Card>

    <Button Click="Button_Click" Width="50" Height="20" Content="Test"/>
</StackPanel>
private void Button_Click( object sender, RoutedEventArgs e ) {
    this.TestCard.IsDetailed= !this.TestCard.IsDetailed;
}

Any Help is apreciated, I'm stuck...

Upvotes: 0

Views: 624

Answers (1)

ASh
ASh

Reputation: 35681

you can benefit a lot if declare Front and Back with object type, not FrameworkElement - mostly because it will allow easy bidnings. you can keep rich visual appearance if you add corresponding templates for those two properties.

public class Card : Control
{
    static Card()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(Card), new FrameworkPropertyMetadata(typeof(Card)));
    }

    public object Front
    {
        get { return (object)GetValue(FrontProperty); }
        set { SetValue(FrontProperty, value); }
    }

    public static readonly DependencyProperty FrontProperty =
        DependencyProperty.Register("Front", typeof(object), typeof(Card), new PropertyMetadata(null));

    public DataTemplate FrontTemplate
    {
        get { return (DataTemplate)GetValue(FrontTemplateProperty); }
        set { SetValue(FrontTemplateProperty, value); }
    }

    public static readonly DependencyProperty FrontTemplateProperty =
        DependencyProperty.Register("FrontTemplate", typeof(DataTemplate), typeof(Card), new PropertyMetadata(null));

    public object Back
    {
        get { return (object)GetValue(BackProperty); }
        set { SetValue(BackProperty, value); }
    }

    public static readonly DependencyProperty BackProperty =
        DependencyProperty.Register("Back", typeof(object), typeof(Card), new PropertyMetadata(null));

    public DataTemplate BackTemplate
    {
        get { return (DataTemplate)GetValue(BackTemplateProperty); }
        set { SetValue(BackTemplateProperty, value); }
    }

    public static readonly DependencyProperty BackTemplateProperty =
        DependencyProperty.Register("BackTemplate", typeof(DataTemplate), typeof(Card), new PropertyMetadata(null));

    public bool IsDetailed
    {
        get { return (bool)GetValue(IsDetailedProperty); }
        set { SetValue(IsDetailedProperty, value); }
    }

    public static readonly DependencyProperty IsDetailedProperty =
        DependencyProperty.Register(nameof(IsDetailed), typeof(bool), typeof(Card), new PropertyMetadata(false));

    public CornerRadius CornerRadius
    {
        get { return (CornerRadius)GetValue(CornerRadiusProperty); }
        set { SetValue(CornerRadiusProperty, value); }
    }
    public static readonly DependencyProperty CornerRadiusProperty =
        DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(Card), new PropertyMetadata(new CornerRadius(0)));
}

Use Trigger on IsDetailed property to flip Fron and Back:

<Style TargetType="{x:Type local:Card}">
    <Setter Property="Background" Value="Khaki"/>
    <Setter Property="BorderBrush" Value="Black"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="CornerRadius" Value="10"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:Card}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        CornerRadius="{TemplateBinding CornerRadius}">
                    <ContentPresenter x:Name="presenter" 
                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Border>

                <ControlTemplate.Triggers>

                    <Trigger Property="IsDetailed" Value="True">
                        <Setter TargetName="presenter" Property="Content" 
                                Value="{Binding Front, RelativeSource={RelativeSource TemplatedParent}}"/>
                        <Setter TargetName="presenter" Property="ContentTemplate"
                                Value="{Binding FrontTemplate, RelativeSource={RelativeSource TemplatedParent}}"/>
                    </Trigger>

                    <Trigger Property="IsDetailed" Value="False">
                        <Setter TargetName="presenter" Property="Content" 
                                Value="{Binding Back, RelativeSource={RelativeSource TemplatedParent}}"/>
                        <Setter TargetName="presenter" Property="ContentTemplate"
                                Value="{Binding BackTemplate, RelativeSource={RelativeSource TemplatedParent}}"/>
                    </Trigger>                        
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

use TemplateBindings inside Template, and RelativeSource TemplatedParent with normal Binding in Setters (TemplateBinding is not supported in Setter).

And here is two examples of usage - with and without binding:

<StackPanel DataContext="{x:Static sys:DateTime.Now}">

    <CheckBox Name="chk_1"/>
    <local:Card Front="Front" 
                Back="Back" 
                IsDetailed="{Binding IsChecked, ElementName=chk_1}"/>


    <CheckBox Name="chk_2"/>
    <local:Card Front="{Binding DayOfWeek}" 
                Back="{Binding TimeOfDay}" 
                IsDetailed="{Binding IsChecked, ElementName=chk_2}">
        <local:Card.FrontTemplate>
            <DataTemplate>
                <TextBlock Foreground="Red" FontWeight="Bold" FontSize="20" 
                           Text="{Binding}" TextDecorations="Underline"/>
            </DataTemplate>
        </local:Card.FrontTemplate>

        <local:Card.BackTemplate>
            <DataTemplate>
                <TextBlock Foreground="Blue" FontWeight="Bold" FontSize="20" 
                           Text="{Binding}" TextDecorations="Underline"/>
            </DataTemplate>
        </local:Card.BackTemplate>
    </local:Card>

</StackPanel>

result:

output

Upvotes: 1

Related Questions