开发者

WPF Cut Corner Element

I am trying to create something similar to the image below in WPF. This control is designed to be the base view of everything in my app and will be sitting inside a Window control with a background (probably a gradient of some kind).

The requirements are as follows:

  • Rounded corners on three sides (top left, bottom 开发者_StackOverflow社区left and bottom right)
  • Cut off tab looking corner on top right side that has the background behind the "cut area" transparent so the background gradient from the Window shows (making it look like it's really been cut off)
  • Title area should be a content container so I could put anything inside it such as icons and text
  • Content area needs to have a minimum height and then grow if the inner content exceeds it (not on the fly - just support the height of whatever elements are in it)

I've been fighting with this for hours, but being new to WPF I'm starting to find myself running around in circles. I think there are major benefits to the flexibility of WPF, but for people just starting out it is almost too daunting.

Any help would be much appreciated! Thanks!

WPF Cut Corner Element


WPF Cut Corner Element

I don't know how to 'fill' the clip so I made the clip in code. Let me know if you need more help adding more properties to control colors etc. Here goes:

Code:

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

    public double DogEar
    {
        get { return (double)GetValue(DogEarProperty); }
        set { SetValue(DogEarProperty, value); }
    }

    public static readonly DependencyProperty DogEarProperty =
        DependencyProperty.Register("DogEar",
        typeof(double), 
        typeof(Tabby),
        new UIPropertyMetadata(8.0, DogEarPropertyChanged));

    private static void DogEarPropertyChanged(
        DependencyObject obj, 
        DependencyPropertyChangedEventArgs e)
    {
        ((Tabby)obj).InvalidateVisual();
    }

    public Tabby()
    {
        this.SizeChanged += new SizeChangedEventHandler(Tabby_SizeChanged);
    }

    void Tabby_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        var clip = new PathGeometry();
        clip.Figures = new PathFigureCollection();
        clip.Figures.Add(
            new PathFigure(
                new Point(0, 0),
                new[] {
                    new LineSegment(new Point(this.ActualWidth - DogEar, 0), true),
                    new LineSegment(new Point(this.ActualWidth, DogEar), true), 
                    new LineSegment(new Point(this.ActualWidth, this.ActualHeight), true),
                    new LineSegment(new Point(0, this.ActualHeight), true) },
                true)
        );
        this.Clip = clip;
    }
}

Generic.xaml

<Style TargetType="{x:Type local:Tabby}">
    <Setter Property="Padding"
            Value="5" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:Tabby}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="auto" />
                        <RowDefinition Height="auto" />
                    </Grid.RowDefinitions>
                    <Border CornerRadius="3,0,0,0"
                            BorderBrush="Black"
                            BorderThickness="1"
                            Background="Black">
                        <ContentPresenter Content="{TemplateBinding Header}"
                                          Margin="{TemplateBinding Padding}" />
                    </Border>
                    <Border CornerRadius="0,0,3,3"
                            BorderBrush="Black"
                            BorderThickness="1"
                            Background="White"
                            Grid.Row="1">

                        <ContentPresenter Content="{TemplateBinding Content}"
                                          Margin="{TemplateBinding Padding}" />
                    </Border>
                </Grid>

            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Using it:

<my:Tabby DogEar="12"
          x:Name="tabby1">
    <my:Tabby.Header>
        <TextBlock Foreground="White">Header</TextBlock>
    </my:Tabby.Header>
    <my:Tabby.Content>
        <TextBlock Text="Content can be anything" />
    </my:Tabby.Content>
</my:Tabby>


Try this to get started:

<Grid Width="100" Height="100">  
    <Border Background="Green" CornerRadius="8,0,8,8">
      <Border.Clip>
        <PathGeometry>
          <PathGeometry.Figures>
            <PathFigure StartPoint="0,0">
              <PathFigure.Segments>
                <LineSegment Point="90,0"/>
                <LineSegment Point="100,10"/>
                <LineSegment Point="100,100"/>
                <LineSegment Point="0,100"/>
              </PathFigure.Segments>
            </PathFigure>
          </PathGeometry.Figures>
        </PathGeometry>
      </Border.Clip>
    </Border>
  </Grid>


Here is some code I put together using a custom control.

Control code:

using System;
using System.Windows;
using System.Windows.Controls;

namespace Test
{
    public class ContentCard : HeaderedContentControl
    {
        static ContentCard()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ContentCard), new FrameworkPropertyMetadata(typeof(ContentCard)));
        }
    }
}

Control xaml (in the Themes/Generic.xaml folder)

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:test="clr-namespace:Test">
    <Style TargetType="{x:Type test:ContentCard}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type test:ContentCard}">
                    <Grid  Background="Transparent">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="30" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="20" />
                        </Grid.ColumnDefinitions>

                        <Border Grid.Row="0" Grid.Column="0" Background="{TemplateBinding Background}" CornerRadius="10,0,0,0" Height="30">
                            <ContentControl Content="{TemplateBinding Header}" VerticalAlignment="Center" Margin="10,0,0,0" />
                        </Border>
                        <Path Grid.Row="0" Grid.Column="1" Fill="{TemplateBinding Background}" Data="M0,0 L20,15 L20,30 L0,30 L0,0Z"/>
                        <Border Grid.Row="1" Grid.ColumnSpan="2" BorderBrush="{TemplateBinding Background}" BorderThickness="1,0,1,1" CornerRadius="0,0,10,10" Padding="5" Background="White">
                            <ContentControl Content="{TemplateBinding Content}" />
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

This is how you use it:

<test:ContentCard Grid.RowSpan="4" Grid.ColumnSpan="2" Margin="200" Background="Black">
    <test:ContentCard.Header>
        <TextBlock Text="Title" Foreground="White" />
    </test:ContentCard.Header>
    <test:ContentCard.Content>
        <TextBlock Text="This is some content" Foreground="Black" />
    </test:ContentCard.Content>
</test:ContentCard>


thanks for the post, this is really awesome! I have modified to make my own corner cut border class and same usage below

public class CornerCutBorder : Border
{

    public CornerRadius CornerCutSize
    {
        get { return (CornerRadius)GetValue(CornerCutSizeProperty); }
        set { SetValue(CornerCutSizeProperty, value); }
    }

    public static readonly DependencyProperty CornerCutSizeProperty = DependencyProperty.Register("CornerCutSize", typeof(CornerRadius),typeof(CornerCutBorder),new UIPropertyMetadata(new CornerRadius(), DogEarPropertyChanged));

    private static void DogEarPropertyChanged(DependencyObject obj,DependencyPropertyChangedEventArgs e)
    {
        ((CornerCutBorder)obj).Resized();
    }

    public CornerCutBorder()
    {
        this.SizeChanged += new SizeChangedEventHandler(Border_SizeChanged);
    }

    void Border_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        Resized();
    }

    void Resized()
    {
        var clip = new PathGeometry();

        clip.Figures = new PathFigureCollection();
        RectangleGeometry rectangleGeometry = new RectangleGeometry(new Rect()
        {
            Width = this.ActualWidth,
            Height = this.ActualHeight
        });

        clip.AddGeometry(rectangleGeometry);

        if (CornerCutSize.TopLeft > 0)
        {
            clip.Figures.Add(
            new PathFigure(
                new Point(0, 0),
                new[] {
                new LineSegment(new Point(CornerCutSize.TopLeft, 0), true),
                new LineSegment(new Point(0, CornerCutSize.TopLeft), true) },
                true)
            );
        }

        if (CornerCutSize.TopRight > 0)
        {
            clip.Figures.Add(
            new PathFigure(
                new Point(this.ActualWidth - CornerCutSize.TopRight, 0),
                new[] {
                new LineSegment(new Point(this.ActualWidth, 0), true),
                new LineSegment(new Point(this.ActualWidth, CornerCutSize.TopRight), true) },
                true)
            );
        }

        if (CornerCutSize.BottomLeft > 0)
        {
            clip.Figures.Add(
            new PathFigure(
                new Point(0, this.ActualHeight - CornerCutSize.BottomLeft),
                new[] {
                new LineSegment(new Point(0, this.ActualHeight), true),
                new LineSegment(new Point(CornerCutSize.BottomLeft, this.ActualHeight), true) },
                true)
            );
        }

        if (CornerCutSize.BottomRight > 0)
        {
            clip.Figures.Add(
            new PathFigure(
                new Point(this.ActualWidth - CornerCutSize.BottomRight, this.ActualHeight),
                new[] {
                new LineSegment(new Point(this.ActualWidth, this.ActualHeight), true),
                new LineSegment(new Point(this.ActualWidth, this.ActualHeight-CornerCutSize.BottomRight), true) },
                true)
            );
        }

        this.Clip = clip;
    }
}


<local:CornerCutBorder CornerCutSize="30,100,100,50" Width="600" Height="400" BorderThickness="2" Background="Red" ></local:CornerCutBorder>

Sample output

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜