Come si crea un contenitore WPF Rounded Corner?


114

Stiamo creando un'applicazione XBAP di cui abbiamo bisogno per avere angoli arrotondati in varie posizioni in una singola pagina e vorremmo avere un contenitore WPF Rounded Corner per posizionare un gruppo di altri elementi all'interno. Qualcuno ha qualche suggerimento o codice di esempio su come possiamo farlo al meglio? O con gli stili su a o con la creazione di un controllo personalizzato?


1
Avvertenza: se metti una singola riga di testo all'interno di un bordo di un rettangolo arrotondato, le persone anziane come me la guarderanno e penseranno: "Pulsante Macintosh anni '80!"
mjfgates

Non hai idea di quanto mi manchi il Macintosh degli anni '80! Penso che questa domanda dovrebbe indicare esplicitamente se si desidera o meno ritagliare gli angoli perché la risposta selezionata non ritaglia il bordo.
ATL_DEV

Risposte:


266

Non hai bisogno di un controllo personalizzato, metti semplicemente il tuo contenitore in un elemento di bordo:

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

Puoi sostituire il <Grid/>con uno qualsiasi dei contenitori di layout ...


30
Per qualsiasi oggetto di spessore (BorderThickness o CornerRadius) è possibile specificare un singolo numero se tutti e 4 sono uguali, ad esempio CornerRadius = "8".
Santiago Palladino

3
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="8">è un sostituto adatto per questo, un po 'più succinto
Kieren Johnstone

@ Patrik Deoghare, non sbaglio, kobusb è fantastico ... ma penso che il team WPF sia stato terribilmente fantastico per averlo integrato e averlo reso così facile da sfruttare. (Anche se si potrebbe sostenere che una moderna piattaforma di interfaccia utente che non dispone di questa funzionalità integrata ... in realtà non è una moderna piattaforma di interfaccia utente.)
cplotts

1
A proposito, l'implementazione di Border è estremamente illuminante ... se hai voglia di scavare sotto le coperte. Ad esempio, come utilizza StreamGeometry ...
cplotts

8
OK, ho ottenuto che funzioni aumentando lo spessore del bordo, ma questa soluzione non ritaglia gli angoli dei bambini all'interno del contenitore. Impedisce solo che gli angoli si sovrappongano al raggio del bordo limitando l'altezza e la larghezza dei bambini. La soluzione di Chris Cavanagh gestisce questo caso. Purtroppo, speravo che questa soluzione funzionasse perché sembra più efficiente ed elegante.
ATL_DEV

54

So che questa non è una risposta alla domanda iniziale ... ma spesso vuoi ritagliare il contenuto interno di quel bordo arrotondato che hai appena creato.

Chris Cavanagh ha escogitato un modo eccellente per farlo.

Ho provato un paio di approcci diversi a questo ... e penso che questo sia eccezionale.

Ecco il xaml di seguito:

<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
I controlli Blacklight ( blacklight.codeplex.com ) hanno anche un piccolo controllo ingegnoso chiamato ClippingBorder che ti consente anche di ritagliare il contenuto agli angoli arrotondati. Una cosa bella di ClippingBorder è che non usa un VisualBrush (che è uno dei pennelli più costosi (in termini di prestazioni)).
cplotts

1
Tuttavia, ho appena dato un'occhiata all'interno dell'implementazione di ClippingBorder ... e utilizza 4 ContentControl (s) nel suo ControlTemplate predefinito (uno per ciascuno degli angoli) ... quindi non sono sicuro se sia più o meno performante rispetto all'approccio VisualBrush sopra. Direi che forse è meno performante.
cplotts

Questa dovrebbe essere la risposta scelta se è necessario il ritaglio.
ATL_DEV

1
Questo è l'unico vero modo per farlo! È sorprendente che questo non sia il comportamento predefinito per gli elementi all'interno di un bordo.
eran otzap

@eranotzap Sono contento che ti sia piaciuta questa risposta. L'unico svantaggio è che stai usando un VisualBrush che è un oggetto più performante. La mia altra risposta qui sotto ti mostra come evitare questo VisualBrush ...
cplotts

14

Dovevo farlo da solo, quindi ho pensato di pubblicare un'altra risposta qui.

Ecco un altro modo per creare un bordo con angoli arrotondati e ritagliare il suo contenuto interno . Questo è il modo semplice utilizzando la proprietà Clip. È bello se vuoi evitare un VisualBrush.

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

Il codice per il convertitore:

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

Soluzione molto interessante. Sto creando un modello di controllo per un pulsante che necessita sia di un bagliore esterno che di un bagliore interno in stati diversi, e questo ha aiutato a risolvere il problema.
Quanta

2

Implementazione basata sul codice VB.Net della soluzione di controllo delle frontiere di kobusb. L'ho usato per popolare un ListBox di controlli Button. I controlli Button vengono creati dalle estensioni MEF. Ogni estensione utilizza l'attributo ExportMetaData di MEF per una descrizione dell'estensione. Le estensioni sono oggetti grafici VisiFire. L'utente preme un pulsante, selezionato dall'elenco dei pulsanti, per eseguire il grafico desiderato.

        ' 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

Se stai cercando di inserire un pulsante in un bordo di un rettangolo arrotondato, dovresti controllare l'esempio di msdn . L'ho trovato cercando su Google le immagini del problema (invece del testo). Il loro ingombrante rettangolo esterno è (per fortuna) facile da rimuovere.

Nota che dovrai ridefinire il comportamento del pulsante (poiché hai modificato il ControlTemplate). Cioè, dovrai definire il comportamento del pulsante quando cliccato utilizzando un tag Trigger (Property = "IsPressed" Value = "true") nel tag ControlTemplate.Triggers. Spero che questo faccia risparmiare a qualcun altro il tempo che ho perso :)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.