Comment créer un conteneur WPF Rounded Corner?

114

Nous créons une application XBAP dont nous avons besoin pour avoir des coins arrondis à différents endroits sur une seule page et nous aimerions avoir un conteneur WPF Rounded Corner pour y placer un tas d'autres éléments. Quelqu'un a-t-il des suggestions ou des exemples de code sur la meilleure façon d'accomplir cela? Soit avec des styles sur un ou avec la création d'un contrôle personnalisé?

FarrEver
la source
1
Attention: si vous mettez une seule ligne de texte à l'intérieur d'une bordure de rectangle arrondi, les personnes âgées comme moi la regarderont et penseront, "bouton poussoir Macintosh des années 80!"
mjfgates
Vous n'avez aucune idée à quel point le Macintosh des années 80 me manque! Je pense que cette question devrait indiquer explicitement si le découpage des coins est souhaité ou non, car la réponse sélectionnée ne coupe pas la bordure.
ATL_DEV

Réponses:

266

Vous n'avez pas besoin d'un contrôle personnalisé, placez simplement votre conteneur dans un élément de bordure:

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

Vous pouvez remplacer le <Grid/>par l'un des conteneurs de disposition ...

kobusb
la source
30
Pour tout objet d'épaisseur (BorderThickness ou CornerRadius), vous pouvez spécifier un seul nombre si les 4 sont identiques, par exemple CornerRadius = "8".
Santiago Palladino
3
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="8">est un remplacement approprié pour cela, un peu plus succint
Kieren Johnstone
@Patrik Deoghare, ne me trompez pas, kobusb est génial ... mais je pense que l'équipe WPF était vraiment géniale pour intégrer cela et en tirer parti si facilement. (Bien que l'on puisse affirmer qu'une plate-forme d'interface utilisateur moderne qui n'a pas cela intégré ... n'est vraiment pas une plate-forme d'interface utilisateur moderne.)
cplotts
1
Soit dit en passant, la mise en œuvre de Border est extrêmement éclairante ... si vous avez envie de fouiller sous les couvertures. Par exemple, comment il utilise StreamGeometry ...
cplotts
8
OK, je l'ai fait fonctionner en augmentant l'épaisseur de la bordure, mais cette solution ne coupe pas les coins des enfants dans le conteneur. Il empêche uniquement les coins de chevaucher le rayon de la bordure en restreignant les hauteurs et les largeurs des enfants. La solution de Chris Cavanagh gère cette affaire. Malheureusement, j'espérais que cette solution fonctionnait car elle semble plus efficace et élégante.
ATL_DEV
54

Je sais que ce n'est pas une réponse à la question initiale ... mais vous voulez souvent couper le contenu intérieur de cette bordure de coin arrondie que vous venez de créer.

Chris Cavanagh a trouvé un excellent moyen de faire exactement cela.

J'ai essayé plusieurs approches différentes à ce sujet ... et je pense que celle-ci est géniale.

Voici le xaml ci-dessous:

<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
la source
1
Les contrôles Blacklight ( blacklight.codeplex.com ) ont également un petit contrôle astucieux appelé ClippingBorder qui vous permet également de couper le contenu à vos coins arrondis. Une bonne chose à propos de ClippingBorder est qu'il n'utilise pas de VisualBrush (qui est l'un des pinceaux les plus coûteux (en termes de performances)).
cplotts
1
Cependant, je viens de jeter un œil à l'implémentation de ClippingBorder ... et il utilise 4 ContentControl (s) dans son ControlTemplate par défaut (un pour chacun des coins) ... donc je ne suis pas sûr si c'est plus ou moins performant que l'approche VisualBrush ci-dessus. Je spéculerais peut-être moins performant.
cplotts
Cela devrait être la réponse choisie si un découpage est nécessaire.
ATL_DEV
1
C'est la seule vraie façon de faire cela! C'est étonnant que ce ne soit pas le comportement par défaut des éléments à l'intérieur d'une bordure.
eran otzap
@eranotzap Heureux que vous ayez aimé cette réponse. Le seul inconvénient est que vous utilisez un VisualBrush qui est un élément plus performant. Mon autre réponse ci-dessous vous montre comment éviter ce VisualBrush ...
cplotts
14

Je devais juste le faire moi-même, alors j'ai pensé que je publierais une autre réponse ici.

Voici une autre façon de créer une bordure d'angle arrondie et de découper son contenu interne . C'est la méthode la plus simple en utilisant la propriété Clip. C'est bien si vous voulez éviter un VisualBrush.

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

Le code du convertisseur:

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
la source
Solution très cool. Je crée un modèle de contrôle pour un bouton qui a besoin à la fois d'une lueur extérieure et d'une lueur intérieure dans différents états, et cela a aidé à résoudre le problème.
Quanta
2

Implémentation basée sur le code VB.Net de la solution de contrôle aux frontières de kobusb. Je l'ai utilisé pour remplir une ListBox de contrôles Button. Les contrôles Button sont créés à partir des extensions MEF. Chaque extension utilise l'attribut ExportMetaData de MEF pour une description de l'extension. Les extensions sont des objets graphiques VisiFire. L'utilisateur appuie sur un bouton, sélectionné dans la liste des boutons, pour exécuter le graphique souhaité.

        ' 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
la source
1

Si vous essayez de placer un bouton dans une bordure de rectangle arrondi, vous devriez consulter l'exemple de msdn . J'ai trouvé cela en recherchant des images du problème sur Google (au lieu du texte). Leur rectangle extérieur volumineux est (heureusement) facile à enlever.

Notez que vous devrez redéfinir le comportement du bouton (puisque vous avez changé le ControlTemplate). Autrement dit, vous devrez définir le comportement du bouton lorsque vous cliquez sur une balise Trigger (Property = "IsPressed" Value = "true") dans la balise ControlTemplate.Triggers. J'espère que cela fera gagner à quelqu'un d'autre le temps que j'ai perdu :)

Daniel
la source