Wie erstelle ich einen WPF Rounded Corner-Container?


114

Wir erstellen eine XBAP-Anwendung, für die an verschiedenen Stellen auf einer Seite abgerundete Ecken erforderlich sind, und wir möchten einen WPF-Container für abgerundete Ecken, in dem eine Reihe anderer Elemente platziert werden können. Hat jemand Vorschläge oder Beispielcode, wie wir dies am besten erreichen können? Entweder mit Stilen auf einem oder mit dem Erstellen eines benutzerdefinierten Steuerelements?


1
Vorsichtsmaßnahme: Wenn Sie eine einzelne Textzeile in einen Rand mit abgerundeten Rechtecken einfügen, werden alte Leute wie ich darauf schauen und denken: "Macintosh '80s Push Button!"
mjfgates

Sie haben keine Ahnung, wie sehr ich den 80er Macintosh vermisse! Ich denke, diese Frage sollte explizit angeben, ob das Abschneiden der Ecken erwünscht ist oder nicht, da die ausgewählte Antwort den Rand nicht abschneidet.
ATL_DEV

Antworten:


266

Sie benötigen kein benutzerdefiniertes Steuerelement. Fügen Sie Ihren Container einfach in ein Rahmenelement ein:

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

Sie können die <Grid/>durch jeden der Layout-Container ersetzen ...


30
Für jedes Dickenobjekt (BorderThickness oder CornerRadius) können Sie eine einzelne Zahl angeben, wenn alle 4 gleich sind, z. B. CornerRadius = "8".
Santiago Palladino

3
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="8">ist ein geeigneter Ersatz dafür, ein bisschen prägnanter
Kieren Johnstone

@Patrik Deoghare, versteh mich nicht falsch, Kobusb ist großartig ... aber ich denke, das WPF-Team war verdammt großartig, weil es dies eingebaut und es so einfach gemacht hat, es auszunutzen. (Obwohl man argumentieren könnte, dass eine moderne UI-Plattform, in der dies nicht eingebaut ist, wirklich keine moderne UI-Plattform ist.)
cplotts

1
Übrigens ist die Implementierung von Border äußerst aufschlussreich ... wenn Sie Lust haben, unter der Decke zu graben. Zum Beispiel, wie es StreamGeometry verwendet ...
cplotts

8
OK, ich habe es durch Erhöhen der Randdicke zum Laufen gebracht, aber diese Lösung schneidet die Ecken der Kinder im Container nicht ab. Es verhindert nur, dass die Ecken den Randradius überlappen, indem die Höhen und Breiten der Kinder eingeschränkt werden. Die Lösung von Chris Cavanagh behandelt diesen Fall. Leider hatte ich gehofft, dass diese Lösung funktioniert, weil sie effizienter und eleganter erscheint.
ATL_DEV

54

Ich weiß, dass dies keine Antwort auf die ursprüngliche Frage ist ... aber Sie möchten häufig den inneren Inhalt des soeben erstellten Randes mit abgerundeten Ecken abschneiden.

Chris Cavanagh hat eine hervorragende Möglichkeit gefunden , genau dies zu tun.

Ich habe ein paar verschiedene Ansätze ausprobiert ... und ich denke, dieser rockt.

Hier ist das xaml unten:

<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
Die Blacklight-Steuerelemente ( blacklight.codeplex.com ) verfügen außerdem über ein kleines Steuerelement namens ClippingBorder, mit dem Sie den Inhalt auch an Ihren abgerundeten Ecken befestigen können. Eine schöne Sache bei ClippingBorder ist, dass kein VisualBrush verwendet wird (was einer der teuersten (in Bezug auf die Leistung) Pinsel ist).
Plots

1
Ich habe mir jedoch nur die Implementierung des ClippingBorder angesehen ... und es verwendet 4 ContentControl (s) in seiner Standard-ControlTemplate (eine für jede der Ecken) ... daher bin ich mir nicht sicher, ob es mehr oder weniger ist performanter als der oben beschriebene VisualBrush-Ansatz. Ich würde vielleicht weniger performant spekulieren.
Plots

Dies sollte die gewählte Antwort sein, wenn ein Clipping erforderlich ist.
ATL_DEV

1
Dies ist der einzig wahre Weg, dies zu tun! Es ist erstaunlich, dass dies nicht das Standardverhalten für Elemente innerhalb eines Rahmens ist.
eran otzap

@eranotzap Schön, dass Ihnen diese Antwort gefallen hat. Der einzige Nachteil ist, dass Sie einen VisualBrush verwenden, der leistungsfähiger ist. Meine andere Antwort unten zeigt Ihnen, wie Sie diesen VisualBrush vermeiden können ...
Plots

14

Ich musste das nur selbst machen, also dachte ich, ich würde hier eine andere Antwort posten.

Hier ist eine andere Möglichkeit, einen abgerundeten Eckrand zu erstellen und seinen inneren Inhalt zu beschneiden . Dies ist der einfache Weg, indem Sie die Clip-Eigenschaft verwenden. Es ist schön, wenn Sie einen VisualBrush vermeiden möchten.

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

Der Code für den Konverter:

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

Sehr coole Lösung. Ich erstelle eine Steuerungsvorlage für eine Schaltfläche, die in verschiedenen Zuständen sowohl ein äußeres als auch ein inneres Leuchten benötigt. Dies hat zur Lösung des Problems beigetragen.
Quanta

2

VB.Net-Code-basierte Implementierung der Grenzkontrolllösung von kobusb. Ich habe es verwendet, um eine ListBox mit Button-Steuerelementen zu füllen. Die Button-Steuerelemente werden aus MEF-Erweiterungen erstellt. Jede Erweiterung verwendet das ExportMetaData-Attribut von MEF für eine Beschreibung der Erweiterung. Die Erweiterungen sind VisiFire-Diagrammobjekte. Der Benutzer drückt eine aus der Liste der Schaltflächen ausgewählte Schaltfläche, um das gewünschte Diagramm auszuführen.

        ' 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

Wenn Sie versuchen, eine Schaltfläche in einen abgerundeten Rechteckrahmen einzufügen, sollten Sie sich das Beispiel von msdn ansehen . Ich fand dies, indem ich nach Bildern des Problems googelte (anstelle von Text). Ihr sperriges äußeres Rechteck ist (zum Glück) leicht zu entfernen.

Beachten Sie, dass Sie das Verhalten der Schaltfläche neu definieren müssen (da Sie die ControlTemplate geändert haben). Das heißt, Sie müssen das Verhalten der Schaltfläche definieren, wenn Sie mit einem Trigger-Tag (Property = "IsPressed" Value = "true") im ControlTemplate.Triggers-Tag darauf klicken. Hoffe das spart jemand anderem die Zeit, die ich verloren habe :)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.