Шаблон кнопочного управления с кругом изменяемого размера

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

<Style x:Key="Button2" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid>
                    <Ellipse Fill="LightGreen" Width="80" Height="80"/>
                    <ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Control.Margin" Value="10"/>
</Style>

Конечно, тот единственные силы все кнопки с помощью того стиля, чтобы иметь круг с диаметром 80 пикселей, независимо от того, как кнопка изменена. Я хотел бы за круг взять короче значений высоты/ширины, так, чтобы он мог динамично масштабироваться согласно калибровке кнопки.

Однако я не считал материала, который преподает, как это может быть сделано в чистом шаблоне XAML? Кажется, что некоторый код - позади требуется, чтобы достигать этого эффекта?

7
задан Dave Clemmer 23 September 2011 в 19:11
поделиться

2 ответа

Здесь в дело вступает TemplateBinding (TemplateBinding используется внутри шаблонов элементов управления и используется для получения значений из шаблонизированного элемента управления, в данном случае кнопки).

<Ellipse Fill="LightGreen" 
    Width="{TemplateBinding ActualWidth}" Height="{TemplateBinding ActualHeight}"/>

Обратите внимание, что это более короткая форма использования:

{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}

Расширение разметки TemplateBinding оптимизировано только для привязки к TemplatedParent.

Учитывая это, если вы хотели, чтобы это был только круг, если ваш эллипс был меньше W/H, то ваше содержимое будет легко вытекать из него, что, я сомневаюсь, является тем, чего вы на самом деле хотите...? Я думал использовать для этого многозначный конвертер, но вы не можете привязаться к параметру конвертера, так что это исключено.

В этом случае сработало бы прикрепленное поведение, но это некрасиво.

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:c="clr-namespace:WpfApplication1"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="Window1" Height="300" Width="300">

    <Grid>
        <Button Content="Yo!" Width="50" Height="30">
            <Button.Template>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <Ellipse Fill="LightGreen" local:ConstrainWidthHeight.ConstrainedWidth="{TemplateBinding ActualWidth}" local:ConstrainWidthHeight.ConstrainedHeight="{TemplateBinding ActualHeight}"/>
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Grid>
                </ControlTemplate>
            </Button.Template>
        </Button>
    </Grid>
</Window>

...и вложенное поведение:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;

namespace WpfApplication1 {
    public class ConstrainWidthHeight {
        public static readonly DependencyProperty ConstrainedWidthProperty =
            DependencyProperty.RegisterAttached( "ConstrainedWidth", typeof( double ), typeof( ConstrainWidthHeight ), new PropertyMetadata( double.NaN, OnConstrainValuesChanged ) );
        public static readonly DependencyProperty ConstrainedHeightProperty =
            DependencyProperty.RegisterAttached( "ConstrainedHeight", typeof( double ), typeof( ConstrainWidthHeight ), new UIPropertyMetadata( double.NaN, OnConstrainValuesChanged ) );

        public static double GetConstrainedHeight( FrameworkElement obj ) {
            return (double) obj.GetValue( ConstrainedHeightProperty );
        }

        public static void SetConstrainedHeight( FrameworkElement obj, double value ) {
            obj.SetValue( ConstrainedHeightProperty, value );
        }

        public static double GetConstrainedWidth( FrameworkElement obj ) {
            return (double) obj.GetValue( ConstrainedWidthProperty );
        }

        public static void SetConstrainedWidth( FrameworkElement obj, double value ) {
            obj.SetValue( ConstrainedWidthProperty, value );
        }

        private static void OnConstrainValuesChanged( object sender, DependencyPropertyChangedEventArgs e ) {
            FrameworkElement element = sender as FrameworkElement;
            if( element != null ) {
                double width = GetConstrainedWidth( element );
                double height = GetConstrainedHeight( element );

                if( width != double.NaN && height != double.NaN ) {
                    double value = Math.Min( width, height );

                    element.Width = value;
                    element.Height = value;
                }
            }
        }
    }
}

Итак, причина, по которой необходимо использовать вложенное поведение (AFAICT во всяком случае), заключается в том, что для центрирования эллипса (в сценарии не-квадрата/не-окружности) необходимо, чтобы HorizontalAlignment и VerticalAlignment были в силе. По умолчанию оба параметра имеют значение Stretch, а когда задано явное значение Width/Height, они ведут себя как Center.

При включенном Stretch="Uniform" ваш эллипс всегда будет физически занимать все пространство, ограничения будут накладываться только на рисунок эллипса. При этом нарисованная фигура эллипса всегда будет начинаться слева вверху. Поэтому в данном случае, если ширина кнопки больше ее высоты, нарисованная часть эллипса не будет отцентрирована вместе с текстом.

Этот код - хороший пример того, что вам, вероятно, не нужно:

<Ellipse Height="{TemplateBinding ActualHeight}" Width="{TemplateBinding ActualWidth}" Fill="LightGreen" Stretch="Uniform" />

... и кнопка, использующая его (с неквадратной шириной/высотой):

<Button Content="YO!" Style="{StaticResource Button2}" Width="120" Height="53" VerticalAlignment="Top"></Button>

Выглядит так:

Уродливый http://www.freeimagehosting.net/uploads/84e62c4982.png

... по сравнению с этим с прикрепленной опцией свойства:

alt text http://www.freeimagehosting.net/uploads/40755babcd.png

7
ответ дан 7 December 2019 в 05:22
поделиться

Set Stretch = Uniform on Ellipse будет автоматически работать как Min (Height, Width). Вам не нужно прикрепленное свойство, как предлагает Адам.

    <Style x:Key="Button2" TargetType="Button">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid>
                        <Ellipse Height="{TemplateBinding Height}" Width="{TemplateBinding Width}" Fill="LightGreen" Stretch="Uniform"/>
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
0
ответ дан 7 December 2019 в 05:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: