¿Cómo creo un contenedor de esquina redondeada de WPF?

114

Estamos creando una aplicación XBAP en la que necesitamos tener esquinas redondeadas en varias ubicaciones en una sola página y nos gustaría tener un contenedor WPF Rounded Corner para colocar un montón de otros elementos dentro. ¿Alguien tiene algunas sugerencias o código de muestra sobre cómo podemos lograr esto mejor? ¿O con estilos en un o con la creación de un control personalizado?

FarrEver
fuente
1
Advertencia: si coloca una sola línea de texto dentro de un borde de rectángulo redondeado, las personas mayores como yo lo mirarán y pensarán: "¡Botón de Macintosh de los 80!"
mjfgates
¡No tienes idea de lo mucho que extraño el Macintosh de los 80! Creo que esta pregunta debería indicar explícitamente si se desea o no recortar las esquinas porque la respuesta seleccionada no recorta el borde.
ATL_DEV

Respuestas:

266

No necesita un control personalizado, simplemente coloque su contenedor en un elemento de borde:

<Border BorderBrush="#FF000000" BorderThickness="1" CornerRadius="8">
   <Grid/>
</Border>

Puede reemplazar el <Grid/>con cualquiera de los contenedores de diseño ...

kobusb
fuente
30
Para cualquier objeto de grosor (BorderThickness o CornerRadius) puede especificar un solo número si los 4 son iguales, como CornerRadius = "8".
Santiago Palladino
3
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="8">es un reemplazo adecuado para esto, un poco más sucinto
Kieren Johnstone
@Patrik Deoghare, no me equivoque, kobusb es increíble ... pero creo que el equipo de WPF fue increíblemente increíble por construir esto y hacer que sea tan fácil de aprovechar. (Aunque se podría argumentar que una plataforma de interfaz de usuario moderna que no tiene esta construido en ... en realidad no es una plataforma de interfaz de usuario moderna.)
cplotts
1
Por cierto, la implementación de Border es extremadamente esclarecedora ... si te apetece escarbar debajo de las sábanas. Por ejemplo, cómo usa StreamGeometry ...
cplotts
8
De acuerdo, lo hice funcionar aumentando el grosor del borde, pero esta solución no recorta las esquinas de los niños dentro del contenedor. Solo evita que las esquinas se superpongan al radio del borde restringiendo las alturas y anchos de los niños. La solución de Chris Cavanagh maneja este caso. Lamentablemente, esperaba que esta solución funcionara porque parece más eficiente y elegante.
ATL_DEV
54

Sé que esta no es una respuesta a la pregunta inicial ... pero a menudo desea recortar el contenido interno del borde de la esquina redondeada que acaba de crear.

Chris Cavanagh ha ideado una manera excelente de hacer precisamente esto.

He intentado un par de enfoques diferentes para esto ... y creo que este es genial.

Aquí está el xaml a continuación:

<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>
cplotts
fuente
1
Blacklight Controls ( blacklight.codeplex.com ) también tiene un pequeño y ingenioso control llamado ClippingBorder que también le permite recortar el contenido en sus esquinas redondeadas. Una cosa buena de ClippingBorder es que no usa un VisualBrush (que es uno de los pinceles de mayor costo (en términos de rendimiento)).
cplotts
1
Sin embargo, solo eché un vistazo dentro de la implementación de ClippingBorder ... y usa 4 ContentControl (s) en su ControlTemplate predeterminado (uno para cada una de las esquinas) ... así que no estoy seguro si es más o menos más eficiente que el enfoque VisualBrush anterior. Yo especularía que tal vez sea menos eficiente.
cplotts
Esta debería ser la respuesta elegida si se requiere recorte.
ATL_DEV
1
¡Esta es la única forma real de hacer esto! Es sorprendente que este no sea el comportamiento predeterminado de los elementos dentro de un borde.
eran otzap
@eranotzap Me alegro de que te haya gustado esta respuesta. El único inconveniente es que está utilizando un VisualBrush, que es un elemento de mayor rendimiento. Mi otra respuesta a continuación le muestra cómo evitar este VisualBrush ...
cplotts
14

Solo tenía que hacer esto yo mismo, así que pensé en publicar otra respuesta aquí.

Aquí hay otra forma de crear un borde de esquina redondeado y recortar su contenido interno . Esta es la forma más sencilla de utilizar la propiedad Clip. Es bueno si desea evitar un VisualBrush.

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

El código del convertidor:

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();
    }
}
cplotts
fuente
Solución muy fresca. Estoy creando una plantilla de control para un botón que necesita tanto un brillo externo como un brillo interno en diferentes estados, y esto ayudó a resolver el problema.
Quanta,
2

Implementación basada en código VB.Net de la solución de control de fronteras de kobusb. Lo usé para llenar un ListBox de controles de botón. Los controles de botón se crean a partir de extensiones MEF. Cada extensión usa el atributo ExportMetaData de MEF para una Descripción de la extensión. Las extensiones son objetos de gráficos VisiFire. El usuario presiona un botón, seleccionado de la lista de botones, para ejecutar el gráfico deseado.

        ' 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
BSalita
fuente
1

Si está intentando colocar un botón en un borde de rectángulo redondeado, debería consultar el ejemplo de msdn . Encontré esto buscando en Google imágenes del problema (en lugar de texto). Su voluminoso rectángulo exterior es (afortunadamente) fácil de quitar.

Tenga en cuenta que tendrá que redefinir el comportamiento del botón (ya que ha cambiado la ControlTemplate). Es decir, necesitará definir el comportamiento del botón cuando se hace clic en él usando una etiqueta Trigger (Property = "IsPressed" Value = "true") en la etiqueta ControlTemplate.Triggers. Espero que esto le salve a alguien más el tiempo que perdí :)

Daniel
fuente