如何创建一个具有WPF圆角的容器?

133

我们正在创建一个XBAP应用程序,在单个页面的各个位置需要有圆角,并且我们想要一个WPF圆角容器来放置一堆其他元素。 有人有什么建议或示例代码,可以帮助我们最好地实现这个功能?可以使用带样式的 <div> 或创建自定义控件来实现。


1
注意事项:如果您将一行文本放在圆角矩形边框内,像我这样的老年人会看到它并认为:“这是麦金塔80年代的按钮!” - mjfgates
你不知道我有多么想念80年代的Macintosh!我认为这个问题应该明确说明是否需要剪裁角落,因为所选答案没有剪裁边框。 - ATL_DEV
5个回答

303

您不需要自定义控件,只需将容器放在边框元素中:

<Border BorderBrush="#FF000000" BorderThickness="1" CornerRadius="8">
   <Grid/>
</Border>
你可以用任何布局容器替换 <Grid/>...

31
对于任何厚度的对象(BorderThickness或CornerRadius),如果4个值都相同,例如CornerRadius="8",则可以指定一个单独的数字。 - Santiago Palladino
3
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="8"> 可以替换为这个,更加简明扼要。 - Kieren Johnstone
1
顺便说一下,Border的实现非常启发人心...如果你想深入了解。例如,它如何使用StreamGeometry... - cplotts
这对我也不起作用,我正在使用.NET 4.5和StackPanel。 - ATL_DEV
11
好的,我通过增加边框厚度来使它工作了,但这种解决方案不能剪切容器内子元素的角落。它只是通过限制子元素的高度和宽度来防止角落重叠边框半径。Chris Cavanagh的解决方案处理了这种情况。可悲的是,我本希望这个解决方案可行,因为它看起来更有效率和优雅。 - ATL_DEV
显示剩余2条评论

61

我知道这不是对最初问题的回答...但你经常想要裁剪刚创建的圆角边框的内部内容。

Chris Cavanagh提出了一种优秀的方法,可以轻松实现。

我尝试过几种不同的方法...我认为这个方法真的很棒。

以下是XAML代码:

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Background="Black"
>
    <!-- Rounded yellow border -->
    <Border
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        BorderBrush="Yellow"
        BorderThickness="3"
        CornerRadius="10"
        Padding="2"
    >
        <Grid>
            <!-- Rounded mask (stretches to fill Grid) -->
            <Border
                Name="mask"
                Background="White"
                CornerRadius="7"
            />

            <!-- Main content container -->
            <StackPanel>
                <!-- Use a VisualBrush of 'mask' as the opacity mask -->
                <StackPanel.OpacityMask>
                    <VisualBrush Visual="{Binding ElementName=mask}"/>
                </StackPanel.OpacityMask>

                <!-- Any content -->
                <Image Source="http://chriscavanagh.files.wordpress.com/2006/12/chriss-blog-banner.jpg"/>
                <Rectangle
                    Height="50"
                    Fill="Red"/>
                <Rectangle
                    Height="50"
                    Fill="White"/>
                <Rectangle
                    Height="50"
                    Fill="Blue"/>
            </StackPanel>
        </Grid>
    </Border>
</Page>

1
Blacklight Controls(http://blacklight.codeplex.com/)还有一个非常方便的控件叫做ClippingBorder,它也允许您将内容剪切到圆角。 ClippingBorder的一个好处是它不使用VisualBrush(这是性能成本最高的笔刷之一)。 - cplotts
1
然而,我刚刚查看了ClippingBorder的实现方式...它在默认的ControlTemplate中使用了4个ContentControl(每个角落一个)...所以我不确定它是否比上面的VisualBrush方法更高效。我猜可能不太高效。 - cplotts
如果需要剪裁,则应选择此答案。 - ATL_DEV
1
这是唯一真正的方法! 令人惊奇的是,这不是边框内元素的默认行为。 - eran otzap
@eranotzap 很高兴你喜欢这个答案。唯一的缺点是你正在使用一个更加耗费性能的 VisualBrush。我下面的另一个答案向你展示了如何避免使用 VisualBrush... - cplotts

20

我之前自己也需要做这个,所以我想在这里发表另一个答案。

这是另一种创建圆角边框并剪切其内部内容的方法。这是使用 Clip 属性的直接方式。如果您想避免使用 VisualBrush,这很好用。

XAML 代码:

<Border
    Width="200"
    Height="25"
    CornerRadius="11"
    Background="#FF919194"
>
    <Border.Clip>
        <RectangleGeometry
            RadiusX="{Binding CornerRadius.TopLeft, RelativeSource={RelativeSource AncestorType={x:Type Border}}}"
            RadiusY="{Binding RadiusX, RelativeSource={RelativeSource Self}}"
        >
            <RectangleGeometry.Rect>
                <MultiBinding
                    Converter="{StaticResource widthAndHeightToRectConverter}"
                >
                    <Binding
                        Path="ActualWidth"
                        RelativeSource="{RelativeSource AncestorType={x:Type Border}}"
                    />
                    <Binding
                        Path="ActualHeight"
                        RelativeSource="{RelativeSource AncestorType={x:Type Border}}"
                    />
                </MultiBinding>
            </RectangleGeometry.Rect>
        </RectangleGeometry>
    </Border.Clip>

    <Rectangle
        Width="100"
        Height="100"
        Fill="Blue"
        HorizontalAlignment="Left"
        VerticalAlignment="Center"
    />
</Border>

转换器的代码:

public class WidthAndHeightToRectConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        double width = (double)values[0];
        double height = (double)values[1];
        return new Rect(0, 0, width, height);
    }
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

非常棒的解决方案。我正在为一个需要在不同状态下同时具有外发光和内发光效果的按钮创建控件模板,这个解决方案帮助我解决了问题。 - Quanta
在我为了处理 OpacityMask 问题苦苦挣扎了几个小时后,特地登录账号给你点赞。 - undefined

2

这是基于VB.Net代码实现的kobusb边框控件解决方案。我使用它来填充一个Button控件的ListBox。这些Button控件是从MEF扩展创建的,每个扩展都使用MEF的ExportMetaData属性为扩展提供了描述信息。这些扩展是VisiFire图表对象。用户可以从按钮列表中选择一个按钮,以执行所需的图表。

        ' Create a ListBox of Buttons, one button for each MEF charting component. 
    For Each c As Lazy(Of ICharts, IDictionary(Of String, Object)) In ext.ChartDescriptions
        Dim brdr As New Border
        brdr.BorderBrush = Brushes.Black
        brdr.BorderThickness = New Thickness(2, 2, 2, 2)
        brdr.CornerRadius = New CornerRadius(8, 8, 8, 8)
        Dim btn As New Button
        AddHandler btn.Click, AddressOf GenericButtonClick
        brdr.Child = btn
        brdr.Background = btn.Background
        btn.Margin = brdr.BorderThickness
        btn.Width = ChartsLBx.ActualWidth - 22
        btn.BorderThickness = New Thickness(0, 0, 0, 0)
        btn.Height = 22
        btn.Content = c.Metadata("Description")
        btn.Tag = c
        btn.ToolTip = "Push button to see " & c.Metadata("Description").ToString & " chart"
        Dim lbi As New ListBoxItem
        lbi.Content = brdr
        ChartsLBx.Items.Add(lbi)
    Next

Public Event Click As RoutedEventHandler

Private Sub GenericButtonClick(sender As Object, e As RoutedEventArgs)
    Dim btn As Button = DirectCast(sender, Button)
    Dim c As Lazy(Of ICharts, IDictionary(Of String, Object)) = DirectCast(btn.Tag, Lazy(Of ICharts, IDictionary(Of String, Object)))
    Dim w As Window = DirectCast(c.Value, Window)
    Dim cc As ICharts = DirectCast(c.Value, ICharts)
    c.Value.CreateChart()
    w.Show()
End Sub

<System.ComponentModel.Composition.Export(GetType(ICharts))> _
<System.ComponentModel.Composition.ExportMetadata("Description", "Data vs. Time")> _
Public Class DataTimeChart
    Implements ICharts

    Public Sub CreateChart() Implements ICharts.CreateChart
    End Sub
End Class

Public Interface ICharts
    Sub CreateChart()
End Interface

Public Class Extensibility
    Public Sub New()
        Dim catalog As New AggregateCatalog()

        catalog.Catalogs.Add(New AssemblyCatalog(GetType(Extensibility).Assembly))

        'Create the CompositionContainer with the parts in the catalog
        ChartContainer = New CompositionContainer(catalog)

        Try
            ChartContainer.ComposeParts(Me)
        Catch ex As Exception
            Console.WriteLine(ex.ToString)
        End Try
    End Sub

    ' must use Lazy otherwise instantiation of Window will hold open app. Otherwise must specify Shutdown Mode of "Shutdown on Main Window".
    <ImportMany()> _
    Public Property ChartDescriptions As IEnumerable(Of Lazy(Of ICharts, IDictionary(Of String, Object)))

End Class

1

如果您想在圆角矩形边框中放置一个按钮,您应该查看msdn的示例。我是通过搜索问题的图像(而不是文本)来找到这个示例的。它们笨重的外部矩形很容易去除。

请注意,您将需要重新定义按钮的行为(因为您已更改了ControlTemplate)。也就是说,您需要在ControlTemplate.Triggers标记中使用Trigger标记(Property="IsPressed" Value="true")定义按钮被点击时的行为。希望这能节省其他人失去的时间 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接