【问题标题】:WPF Cut Corner ElementWPF 切角元素
【发布时间】:2011-06-23 19:48:31
【问题描述】:

我正在尝试在 WPF 中创建类似于下图的内容。这个控件被设计为我的应用程序中所有内容的基本视图,并将位于带有背景(可能是某种渐变)的 Window 控件中。

要求如下:

  • 三边圆角(左上、左下和右下)
  • 截断标签页右上角的角,“剪切区域”后面的背景透明,因此窗口的背景渐变显示(使它看起来真的被截断了)
  • 标题区域应该是一个内容容器,这样我就可以在其中放置任何东西,例如图标和文本
  • 内容区域需要有一个最小高度,然后在内部内容超过它时增长(不是即时 - 只支持其中任何元素的高度)

我已经为此奋斗了好几个小时,但是作为 WPF 的新手,我开始发现自己在兜圈子。我认为 WPF 的灵活性有很大的好处,但对于刚入门的人来说,这几乎是太令人生畏了。

任何帮助将不胜感激!谢谢!

【问题讨论】:

    标签: c# wpf xaml


    【解决方案1】:

    我不知道如何“填充”剪辑,所以我用代码制作了剪辑。如果您需要更多帮助来添加更多属性以控制颜色等,请告诉我。 如下:

    代码:

    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>
    

    使用它:

    <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>
    

    【讨论】:

    • 完美!!这完全符合我的需要。现在我似乎总是从 WPF 中重新提出一个问题......你到底是怎么学会做的?像这样以有用的方式真正学习 WPF 的资源似乎非常稀缺。
    • 五年前,我做了你现在所处的困难部分。我阅读了书籍,编写了多个项目,犯了错误并询问了其他人(当时并没有那么多,但我很幸运能够接触到一些从事 WPF 工作的人)。即使现在我还在学习。坚持住,你会到达那里的。
    【解决方案2】:

    试试这个开始:

    <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>
    

    【讨论】:

    • 我认为这将是完美的!谢谢!!
    • 您的方法效果很好,但下面的答案(“Tabby”控件)最终对我来说效果更好,因为我需要一些易于重复使用的东西。不过,我为你投了赞成票。再次感谢!
    • 我一开始也是这样,但很快就卡住了,因为我不知道控件的大小,不得不推断 Clip 的大小。
    【解决方案3】:

    这是我使用自定义控件编写的一些代码。

    控制代码:

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

    控制 xaml(在 Themes/Generic.xaml 文件夹中)

    <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>
    

    这是你使用它的方式:

    <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>
    

    【讨论】:

      【解决方案4】:

      感谢您的帖子,这真是太棒了! 我已经修改以制作我自己的切角边框类和下面的相同用法

      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

      【讨论】:

        猜你喜欢
        • 2016-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-19
        • 2015-05-18
        • 1970-01-01
        • 2020-11-16
        相关资源
        最近更新 更多