2010-02-16 21 views
6

Estoy aprendiendo sobre las plantillas de control en WPF y comprobando cómo reemplazar el aspecto del botón con estilos de plantilla personalizados. Veo que para hacer un botón circular, se debe definir una Elipse con la misma altura y ancho.Plantilla de control de botones con círculo redimensionable

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

Por supuesto, que sólo obliga a todos los botones usando ese estilo para tener un círculo con un diámetro de 80 píxeles, independientemente de cómo se cambia el tamaño del botón. Me gustaría que el círculo adopte los valores de altura/ancho más cortos, de modo que pueda escalar dinámicamente de acuerdo con el tamaño del botón.

Sin embargo, no he leído ningún material que enseñe cómo se puede hacer esto en una plantilla XAML pura. Parece que se requiere algún código subyacente para lograr este efecto.

Respuesta

6

Aquí es donde entra en juego TemplateBinding (TemplateBinding se usa dentro de las plantillas de control y se usa para recuperar valores del control de plantilla, en este caso el Botón).

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

Tenga en cuenta que esta es una forma más corta de utilizar:

{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}} 

La extensión de marcado TemplateBinding es sólo optimizado para sólo fijaciones TemplatedParent.

Dicho esto, si quisieras que fuera solo un círculo, si tu elipse fuera menor que la W/H, entonces tu contenido fluirá fácilmente, lo que dudo es lo que realmente deseas ...? Pensé en usar un convertidor multivalor para hacer eso, pero no se puede vincular al parámetro del convertidor, así que eso está fuera.

En ese caso, un comportamiento adjunto funcionaría, pero no es bonito.

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

... y el comportamiento adjunto:

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

bien, ahora la razón de por qué el uso se requiere un comportamiento adjunto (AFAICT de todos modos), es que con el fin de centrar la elipse (en una escenario no cuadrado/no circular), necesita la Alineación Horizontal y la Alineación Vertical para poder tener efecto. El valor predeterminado de ambos es Estirar, y cuando se establece un Ancho/Alto explícito, se comporta como Centro.

Con Estirar = "Uniforme" activado, su Elipse siempre ocupará físicamente todo el espacio, solo el dibujo de la Elipse quedará restringido. Usando esto, tu figura de Elipse dibujada siempre comenzará en la esquina superior izquierda. Entonces, en este caso, si su botón es más ancho que alto, la parte dibujada de la elipse no se centrará junto con el texto.

Este código es un buen ejemplo de lo que es probable que no buscas:

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

... y el botón de usarlo (con una anchura/altura no cuadrados):

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

se ve así:

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

...en comparación con esto con la opción de propiedad adjunta:

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

+0

bien he intentado este método y parece aplicar HorizontalAlignment = "centro" VerticalAlignment = "centro" de la elipse también hace que la forma del círculo desaparece después de cambiar el tamaño de un gran tamaño. Me pregunto qué está pasando. – icelava

+0

Actualicé mi respuesta para usar ActualWidth y ActualHeight. La vinculación a W/H es la razón por la que está haciendo lo que está viendo. –

+0

Después de cambiar a ActualWidth y ActualHeight, parece renderizarse en el centro ahora. Aunque todavía no entiendo completamente por qué, gracias. – icelava

0

Conjunto Stretch = Uniforme de elipse que funciona automáticamente como mínimo (altura, anchura) Usted no necesita una propiedad que se adjunta como sugiere como Adán.

<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

Este formato funciona pero cuando agrego HorizontalAlignment = "Center" VerticalAlignment = "Center" al elemento Ellipse para centralizarlo con ContentPresenter, desaparece. ¿Algunas ideas? – icelava

1

Me encontré con esto.

Tuve un objeto de elipse con stretch=uniform para llenar una cuadrícula, pero no salgo de los límites. Esto funciona bien, sin embargo, el círculo no estaría centrado en caso de que la cuadrícula no fuera perfectamente cuadrada.

La colocación de la elipse en un negatoscopio resolvió el problema para mí:

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

El único problema aquí es que el tamaño de la base de la elipse es 0 (o 1, no estoy seguro), lo que significa que el uso de una stroke llenará automáticamente toda la elipse (el trazo se establecerá en la elipse de tamaño 0, y luego se redimensionará la elipse).

Con un derrame cerebral:

Una solución para la carrera no está trabajando, es utilizar un MinHeight/MinWidth de la elipse:

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

Utilizando el siguiente estilo:

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

(Tenga en cuenta que StrokeThickness ahora es relativo)

Resultado:

Centered ellipse with stroke

Cuestiones relacionadas