带有可调整大小的圆圈的按钮控件模板

发布于 2024-08-21 15:34:48 字数 822 浏览 9 评论 0原文

我正在学习 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 模板中完成此操作的材料?看来需要一些后台代码才能达到这个效果?

I am learning about control templates in WPF and checking out how to replace the button look with custom template styles. I see that to make a circle button, a Ellipse has to be defined with the same height and width.

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

Of course, that only forces all buttons using that style to have a circle with a diameter of 80 pixels, regardless of how the button is resized. I'd like for the circle to take on the shorter of the height/width values, so that it can dynamically scale according to the button sizing.

However i have not read any material that teaches how this can be done in pure XAML template? It seems that some code-behind is required to achieve this effect?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

喜爱纠缠 2024-08-28 15:34:48

这就是 TemplateBinding 的用武之地(TemplateBinding 在控件模板内部使用,用于从模板化控件(在本例中为 Button)检索值。

<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)是为了使椭圆居中(在非方形/非圆形场景中),您需要 Horizo​​ntalAlignment和 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

...与附加属性选项相比:

替代文本 http://www.freeimagehosting.net/uploads/40755babcd.png

This is where TemplateBinding comes in (TemplateBinding is used inside control templates and is used to retrieve values from the templated control, in this case the Button).

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

Note that this is a shorter form of using:

{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}

The TemplateBinding markup extension is just optimized for only TemplatedParent bindings.

That said, if you wanted it to be a circle only, if your ellipse was the smaller of W/H, then your content will easily flow out of it, which I doubt is what you actually want..? I had thought of using a multi value converter to do that, but you can't bind to the converter parameter, so that's out.

In that case, an attached behavior would work, but it's not pretty.

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

...and the attached behavior:

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

Okay, now the reason why using an attached behavior is required (AFAICT anyway), is that in order to center the ellipse (in a non-square/non-circle scenario), you need the HorizontalAlignment and VerticalAlignment to be able to take effect. The default value of both is Stretch, and when an explicit Width/Height is set, it behaves like Center.

With Stretch="Uniform" on, your Ellipse will always physically take up the whole space, it's only the drawing of the Ellipse that will be constrained. Using this, your drawn Ellipse figure will always start at the top left. So in this case if your button is Wider than it is tall, the drawn portion of the Ellipse won't get centered along with the text.

This code is a good example of what you are probably not looking for:

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

...and the button using it (with a non-square width/height):

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

Looks like this:

Ugly http://www.freeimagehosting.net/uploads/84e62c4982.png

... compared to this with the attached property option:

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

风苍溪 2024-08-28 15:34:48

我自己刚刚遇到了这个。

我有一个带有 stretch=uniform 的椭圆对象来填充网格,但不会超出范围。这工作正常,但是,如果网格不是完美的正方形,圆将不会居中。

将椭圆放在视图框中解决了我的问题:

<Grid>
    <Viewbox>
        <Ellipse Stretch="Uniform"/>
    </Viewbox>
</Grid>

这里唯一的问题是椭圆的基本尺寸为0(或1,不确定),这意味着使用笔画将自动填充整个椭圆(描边将设置在 0 大小的椭圆上,然后调整椭圆的大小)。

使用描边:

解决描边不起作用的方法是对椭圆使用 MinHeight/MinWidth:

<Grid Background="Yellow">
    <Viewbox>
        <Ellipse Style="{Binding Path=StyleStatus, ConverterParameter='CoilEllipse', Converter={StaticResource styleConverter}}"/>
    </Viewbox>
</Grid>

使用以下样式:(

<Style x:Key="BaseCoilEllipse" TargetType="Ellipse">
    <Setter Property="MinHeight" Value="10" />
    <Setter Property="MinWidth" Value="10" />
    <!-- Because the ellipse is inside a viewbox, the stroke will be dependant on MinHight/MinWidth. 
         Basically the stroke will now be 4/10th of the ellipse (strokethickness*2 / MinHeight/MinWidth) -->
    <Setter Property="StrokeThickness" Value="2"/>
    <Setter Property="Stroke" Value="Green" />

    <Setter Property="Stretch" Value="Uniform" />
    <Setter Property="Fill" Value="Black"/>
</Style>

请注意,StrokeThickness 现在是相对的 )

结果:

带描边的居中椭圆

Just ran into this myself.

I had an ellipse object with stretch=uniform to fill a grid, but not go out of bounds. This works ok, however, the circle would not be centered in case the grid was not perfectly square.

Placing the ellipse in a viewbox solved the issue for me:

<Grid>
    <Viewbox>
        <Ellipse Stretch="Uniform"/>
    </Viewbox>
</Grid>

The only problem here is that the base size of the ellipse is 0 (or 1, not sure), which means that using a stroke will automatically fill the entire ellipse (the stroke will be set on the 0-size-ellipse, and then the ellipse will be resized).

With a stroke:

A workaround for the stroke not working, is to use a MinHeight/MinWidth for the ellipse:

<Grid Background="Yellow">
    <Viewbox>
        <Ellipse Style="{Binding Path=StyleStatus, ConverterParameter='CoilEllipse', Converter={StaticResource styleConverter}}"/>
    </Viewbox>
</Grid>

Using the following style:

<Style x:Key="BaseCoilEllipse" TargetType="Ellipse">
    <Setter Property="MinHeight" Value="10" />
    <Setter Property="MinWidth" Value="10" />
    <!-- Because the ellipse is inside a viewbox, the stroke will be dependant on MinHight/MinWidth. 
         Basically the stroke will now be 4/10th of the ellipse (strokethickness*2 / MinHeight/MinWidth) -->
    <Setter Property="StrokeThickness" Value="2"/>
    <Setter Property="Stroke" Value="Green" />

    <Setter Property="Stretch" Value="Uniform" />
    <Setter Property="Fill" Value="Black"/>
</Style>

(Note that the StrokeThickness is now relative)

Result:

Centered ellipse with stroke

橘和柠 2024-08-28 15:34:48

在椭圆上设置 Stretch=Uniform 将自动像 Min(Height,Width) 一样工作,您不需要像 Adam 建议的那样需要附加属性。

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

Set Stretch=Uniform on Ellipse will automatically works like Min(Height,Width) You dont need an attached property as like Adam suggests.

    <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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文