Como faço para criar um contêiner de canto arredondado WPF?

114

Estamos criando um aplicativo XBAP no qual precisamos ter cantos arredondados em vários locais em uma única página e gostaríamos de ter um contêiner de canto arredondado WPF para colocar vários outros elementos dentro. Alguém tem algumas sugestões ou exemplos de código sobre como podemos fazer isso da melhor maneira? Com estilos em um ou com a criação de um controle personalizado?

FarrEver
fonte
1
Advertência: se você colocar uma única linha de texto dentro de uma borda de retângulo arredondado, pessoas velhas como eu vão olhar para ela e pensar: "Botão de pressão do Macintosh dos anos 80!"
mjfgates
Você não tem ideia do quanto sinto falta do Macintosh dos anos 80! Acho que esta pergunta deve declarar explicitamente se é ou não desejado cortar os cantos, porque a resposta selecionada não corta a borda.
ATL_DEV

Respostas:

266

Você não precisa de um controle personalizado, apenas coloque seu contêiner em um elemento de borda:

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

Você pode substituir o <Grid/>por qualquer um dos contêineres de layout ...

kobusb
fonte
30
Para qualquer objeto de espessura (BorderThickness ou CornerRadius), você pode especificar um único número se todos os 4 forem iguais, como CornerRadius = "8".
Santiago Palladino
3
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="8">é um substituto adequado para isso, um pouco mais sucinto
Kieren Johnstone
@Patrik Deoghare, não me engane, kobusb é incrível ... mas acho que a equipe WPF foi incrível por construir isso e torná-lo tão fácil de tirar proveito. (Embora alguém possa argumentar que uma plataforma de IU moderna que não tem isso integrado ... realmente não é uma plataforma de IU moderna.)
cplotts
1
A propósito, a implementação do Border é extremamente esclarecedora ... se você quiser se esconder. Por exemplo, como ele usa StreamGeometry ...
cplotts
8
OK, comecei a trabalhar aumentando a espessura da borda, mas essa solução não corta os cantos dos filhos dentro do contêiner. Ele apenas evita que os cantos se sobreponham ao raio da borda, restringindo as alturas e larguras dos filhos. A solução de Chris Cavanagh cuida desse caso. Infelizmente, eu esperava que essa solução funcionasse porque parece mais eficiente e elegante.
ATL_DEV
54

Eu sei que esta não é uma resposta à pergunta inicial ... mas muitas vezes você deseja cortar o conteúdo interno da borda arredondada que acabou de criar.

Chris Cavanagh descobriu uma excelente maneira de fazer exatamente isso.

Eu tentei algumas abordagens diferentes para isso ... e acho que esta é demais.

Aqui está o xaml abaixo:

<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
fonte
1
Os Blacklight Controls ( blacklight.codeplex.com ) também possuem um pequeno controle bacana chamado ClippingBorder, que também permite que você prenda o conteúdo em seus cantos arredondados. Uma coisa boa sobre ClippingBorder é que ele não usa um VisualBrush (que é um dos pincéis de maior custo (em termos de desempenho)).
cplotts
1
No entanto, acabei de dar uma olhada na implementação do ClippingBorder ... e ele usa 4 ContentControl (s) em seu ControlTemplate padrão (um para cada um dos cantos) ... então não tenho certeza se é mais ou menos desempenho do que a abordagem VisualBrush acima. Eu especularia talvez menos desempenho.
cplotts de
Esta deve ser a resposta escolhida se o recorte for necessário.
ATL_DEV
1
Esta é a única maneira real de fazer isso! É incrível que este não seja o comportamento padrão para elementos dentro de uma borda.
eran otzap
@eranotzap Que bom que gostou desta resposta. A única desvantagem é que você está usando um VisualBrush, que é um item de desempenho mais pesado. Minha outra resposta abaixo mostra como evitar esse VisualBrush ...
cplotts
14

Eu só tinha que fazer isso sozinho, então pensei em postar outra resposta aqui.

Esta é outra maneira de criar uma borda de canto arredondado e cortar seu conteúdo interno . Esta é a maneira direta de usar a propriedade Clip. É bom se você quiser evitar um VisualBrush.

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

O código do conversor:

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
fonte
Solução muito legal. Estou criando um modelo de controle para um botão que precisa de um brilho externo e interno em diferentes estados, e isso ajudou a resolver o problema.
Quanta de
2

Implementação baseada em código VB.Net da solução de controle Border da kobusb. Usei-o para preencher um ListBox de controles de botão. Os controles de botão são criados a partir de extensões MEF. Cada extensão usa o atributo ExportMetaData do MEF para uma descrição da extensão. As extensões são objetos gráficos VisiFire. O usuário pressiona um botão, selecionado na lista de botões, para executar o gráfico desejado.

        ' 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
fonte
1

Se você está tentando colocar um botão em uma borda de retângulo arredondado, você deve verificar o exemplo do msdn . Eu encontrei isso pesquisando imagens do problema (em vez de texto). Seu retângulo externo volumoso é (felizmente) fácil de remover.

Observe que você terá que redefinir o comportamento do botão (já que alterou o ControlTemplate). Ou seja, você precisará definir o comportamento do botão quando clicado usando uma tag Trigger (Property = "IsPressed" Value = "true") na tag ControlTemplate.Triggers. Espero que isso economize o tempo que perdi para outra pessoa :)

Daniel
fonte