Как создать контейнер WPF Rounded Corner?

114

Мы создаем приложение XBAP, в котором нам нужно иметь закругленные углы в различных местах на одной странице, и мы хотели бы иметь контейнер WPF Rounded Corner для размещения множества других элементов внутри. Есть ли у кого-нибудь предложения или примеры кода, как мы можем лучше всего этого добиться? Либо со стилями на a, либо с созданием собственного элемента управления?

FarrEver
источник
1
Предостережение: если вы поместите одну строку текста внутри рамки с закругленными прямоугольниками, старики, такие как я, посмотрят на нее и подумают: «Кнопка Macintosh 80-х!»
mjfgates
Вы не представляете, как сильно я скучаю по Macintosh 80-х! Я думаю, что в этом вопросе следует явно указать, желательно ли обрезать углы, потому что выбранный ответ не обрезает границу.
ATL_DEV

Ответы:

266

Вам не нужен настраиваемый элемент управления, просто поместите свой контейнер в элемент границы:

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

Вы можете заменить <Grid/>любой из контейнеров макета ...

kobusb
источник
30
Для объекта любой толщины (BorderThickness или CornerRadius) вы можете указать одно число, если все 4 одинаковые, например CornerRadius = "8".
Сантьяго Палладино,
3
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="8">- подходящая замена для этого, более лаконичная
Кирен Джонстон
@Patrik Deoghare, поймите меня неправильно, kobusb - это круто ... но я думаю, что команда WPF чертовски здорово встраивала это и делала его таким легким в использовании. (Хотя можно утверждать, что современная платформа пользовательского интерфейса, не имеющая этого встроенного ... на самом деле не является современной платформой пользовательского интерфейса.)
cplotts
1
Кстати, реализация Border чрезвычайно поучительна ... если вам хочется копаться под одеялом. Например, как он использует StreamGeometry ...
cplotts
8
Хорошо, я заставил это работать, увеличив толщину границы, но это решение не обрезает углы дочерних элементов внутри контейнера. Это только предотвращает наложение углов на радиус границы, ограничивая высоту и ширину дочерних элементов. Решение Криса Кавана справляется с этим случаем. К сожалению, я надеялся, что это решение сработало, потому что оно кажется более эффективным и элегантным.
ATL_DEV
54

Я знаю, что это не ответ на первоначальный вопрос ... но вы часто хотите обрезать внутреннее содержимое только что созданной границы с закругленными углами.

Крис Кавана придумал отличный способ сделать это.

Я пробовал несколько разных подходов к этому ... и я думаю, что это круто.

Вот xaml ниже:

<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
источник
1
Элементы управления Blacklight ( blacklight.codeplex.com ) также имеют изящный маленький элемент управления под названием ClippingBorder, который также позволяет обрезать содержимое по закругленным углам. Одна из приятных особенностей ClippingBorder заключается в том, что в нем не используется VisualBrush (одна из самых дорогих (с точки зрения производительности) кистей).
график
1
Однако я просто заглянул внутрь реализации ClippingBorder ... и он использует 4 ContentControl (s) в своем ControlTemplate по умолчанию (по одному для каждого из углов) ... поэтому я не уверен, более или менее эффективнее, чем подход VisualBrush, описанный выше. Я бы предположил, что может быть менее эффективным.
график
Это должен быть ответ, выбранный, если требуется обрезка.
ATL_DEV
1
Это единственный реальный способ сделать это! Удивительно, что это не поведение по умолчанию для элементов внутри рамки.
eran otzap
@eranotzap Рад, что вам понравился этот ответ. Единственным недостатком является то, что вы используете VisualBrush, который требует более высокой производительности. Мой другой ответ ниже показывает вам, как избежать этой VisualBrush ...
cplotts
14

Мне просто пришлось сделать это самому, поэтому я подумал, что опубликую здесь еще один ответ.

Вот еще один способ создать границу с закругленными углами и обрезать ее внутреннее содержимое . Это самый простой способ с использованием свойства Clip. Это хорошо, если вы хотите избежать использования VisualBrush.

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>

Код конвертера:

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
источник
Очень крутое решение. Я создаю шаблон управления для кнопки, которая требует как внешнего, так и внутреннего свечения в разных состояниях, и это помогло решить проблему.
Quanta,
2

Реализация решения kobusb по контролю за границами на основе кода VB.Net. Я использовал его для заполнения ListBox элементов управления Button. Элементы управления Button создаются из расширений MEF. Каждое расширение использует атрибут MEF ExportMetaData для описания расширения. Расширения представляют собой объекты диаграмм VisiFire. Пользователь нажимает кнопку, выбранную из списка кнопок, для выполнения желаемого графика.

        ' 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
источник
1

Если вы пытаетесь поместить кнопку в рамку прямоугольника с закругленными углами, вам следует ознакомиться с примером msdn . Я нашел это, поискав в Google изображения проблемы (вместо текста). Их громоздкий внешний прямоугольник (к счастью) легко удалить.

Обратите внимание, что вам придется переопределить поведение кнопки (поскольку вы изменили ControlTemplate). То есть вам нужно будет определить поведение кнопки при нажатии с помощью тега Trigger (Property = "IsPressed" Value = "true") в теге ControlTemplate.Triggers. Надеюсь, это сэкономит кому-то время, которое я потерял :)

Даниил
источник