Dibujar formas

Aprenda a dibujar formas, como elipses, rectángulos, polígonos y trazados. La clase trazado es la forma de visualizar un lenguaje de dibujo basado en vectores bastante complejo en una UI XAML; por ejemplo, puede dibujar curvas Bézier.

Dos conjuntos de clases definen una región del espacio en la UI XAML: Las clases Forma y las clases Geometría. La principal diferencia entre estas clases es que una Forma tiene un pincel asociado a ella y puede ser renderizada en la pantalla, y una Geometría simplemente define una región de espacio y no es renderizada a menos que ayude a contribuir con información a otra propiedad UI. Puedes pensar en una Forma como un UIElement con sus límites definidos por una Geometría. Este tema cubre principalmente las clases de Forma.

Las clases de Forma son Línea, Elipse, Rectángulo, Polígono, Polilínea y Trazado. El trazado es interesante porque puede definir una geometría arbitraria, y la clase Geometría está involucrada aquí porque es una forma de definir las partes de un trazado.

UWP y WinUI 2

Importante

La información y los ejemplos de este artículo están optimizados para aplicaciones que usan el SDK de Aplicaciones para Windows y WinUI 3, pero generalmente son aplicables a las aplicaciones para UWP que usan WinUI 2. Consulte el material de referencia de las API de UWP para obtener información y ejemplos específicos de la plataforma.

Esta sección contiene información que necesita para usar el control en una aplicación para UWP o WinUI 2.

Las API para estas formas existen en el espacio de nombres Windows.UI.Xaml.Shapes.

Relleno y trazo de formas

Para que una forma se muestre en el lienzo de la aplicación, debe asociarle un pincel. Establezca la propiedad Relleno de la forma en el pincel que desee. Para más información acerca de los pinceles, ver Uso de pinceles.

Una forma también puede tener un trazo, que es una línea que se dibuja alrededor del perímetro de la forma. Un trazo también requiere un pincel que defina su apariencia, y debe tener un valor distinto de cero para el grosor del trazo. El grosor del trazo es una propiedad que define el grosor del perímetro alrededor del borde de la forma. Si no especifica un valor de Pincel para Trazo, o si establece el Grosor de Trazo en 0, entonces el borde alrededor de la forma no se dibuja.

Elipse

Una elipse es una forma con un perímetro curvo. Para crear una Elipse básica, especifique un Ancho, un Alto y un Pincel para el Relleno.

El siguiente ejemplo crea una Elipse con un Ancho de 200 y un Alto de 200, y usa un pincel SolidColorBrush de color SteelBlue como relleno.

<Ellipse Fill="SteelBlue" Height="200" Width="200" />
var ellipse1 = new Ellipse();
ellipse1.Fill = new SolidColorBrush(Colors.SteelBlue);
ellipse1.Width = 200;
ellipse1.Height = 200;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(ellipse1);

Esta es la Elipse representada.

A rendered Ellipse.

En este caso, la elipse es lo que la mayoría de la gente consideraría un círculo, pero así es como se declara una forma circular en XAML: se usa una elipse con la misma anchura y altura.

Cuando una elipse se coloca en un diseño de UI, se supone que su tamaño es el mismo que el de un rectángulo con esa anchura y altura; el área fuera del perímetro no se renderiza, pero sigue formando parte del tamaño de la ranura de diseño.

Un conjunto de 6 elementos de elipse forman parte de la plantilla de control para el control ProgressRing, y 2 elementos concéntricos de elipse forman parte de un RadioButton.

Rectángulo

Un Rectángulo es una forma de cuatro lados con sus lados opuestos iguales. Para crear un Rectángulo básico, especifique un ancho, un alto y un relleno.

Puede redondear las esquinas de un Rectángulo. Para crear esquinas redondeadas, especifique un valor para las propiedades RadioX y RadioY. Estas propiedades especifican el eje x y el eje y de una elipse que define la curva de las esquinas. El valor máximo permitido de RadiusX es el ancho dividido por dos y el valor máximo permitido de RadiusY es el alto dividido por dos.

El siguiente ejemplo crea un Rectángulo con un ancho de 200 y un alto de 100. Usa un valor Azul de SolidColorBrush para su Relleno y un valor Negro de SolidColorBrush para su Trazo. Establecemos el Grosor del Trazo en 3. Establecemos la propiedad RadioX en 50 y la propiedad RadioY en 10, lo que da al Rectángulo esquinas redondeadas.

<Rectangle Fill="Blue"
           Width="200"
           Height="100"
           Stroke="Black"
           StrokeThickness="3"
           RadiusX="50"
           RadiusY="10" />
var rectangle1 = new Rectangle();
rectangle1.Fill = new SolidColorBrush(Colors.Blue);
rectangle1.Width = 200;
rectangle1.Height = 100;
rectangle1.Stroke = new SolidColorBrush(Colors.Black);
rectangle1.StrokeThickness = 3;
rectangle1.RadiusX = 50;
rectangle1.RadiusY = 10;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(rectangle1);

Este es el Rectángulo representado.

A rendered Rectangle.

Hay algunos escenarios para definiciones de UI donde en lugar de usar un Rectángulo, un Borde podría ser más apropiado. Si su intención es crear una forma rectangular alrededor de otro contenido, puede ser mejor usar un Borde porque puede tener contenido secundario y se dimensionará automáticamente alrededor de ese contenido, en lugar de usar las dimensiones fijas para alto y ancho como hace el Rectángulo. Un borde también tiene la opción de tener esquinas redondeadas si establece la propiedad CornerRadius.

Por otro lado, un Rectángulo es probablemente una mejor opción para la composición de controles. Una forma Rectángulo se ve en muchas plantillas de control porque se usa como parte "FocusVisual" para controles enfocables. Siempre que el control esté en un estado visual "Enfocado", este rectángulo se hace visible, en otros estados está oculto.

Polígono

Un polígono es una forma con un límite definido por un número arbitrario de puntos. El límite se crea conectando una línea de un punto al siguiente, con el último punto conectado al primero. La propiedad Puntos define la colección de puntos que forman el límite. En XAML, los puntos se definen con una lista separada por comas. En código subyacente se usa una PointCollection para definir los puntos y se agrega cada punto individual como un valor de Punto a la colección.

No es necesario declarar explícitamente los puntos de forma que el punto inicial y el punto final se especifiquen como el mismo valor de Punto. La lógica de renderizado para un polígono asume que se trata de una forma cerrada y conectará implícitamente el punto final con el punto inicial.

El siguiente ejemplo crea un polígono con 4 puntos establecidos en (10,200), (60,140), (130,140) y (180,200). Usa un valor LightBlue de SolidColorBrush para su Relleno, y no tiene valor para Trazo por lo que no tiene contorno perimetral.

<Polygon Fill="LightBlue"
         Points="10,200,60,140,130,140,180,200" />
var polygon1 = new Polygon();
polygon1.Fill = new SolidColorBrush(Colors.LightBlue);

var points = new PointCollection();
points.Add(new Windows.Foundation.Point(10, 200));
points.Add(new Windows.Foundation.Point(60, 140));
points.Add(new Windows.Foundation.Point(130, 140));
points.Add(new Windows.Foundation.Point(180, 200));
polygon1.Points = points;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(polygon1);

Este es el polígono representado.

A rendered Polygon.

Sugerencia

Un valor Punto se usa a menudo como un tipo en XAML para escenarios diferentes a la declaración de los vértices de las formas. Por ejemplo, un Punto es parte de los datos de evento para los eventos táctiles, por lo que puede saber exactamente donde en un espacio de coordenadas ocurrió la acción táctil. Para más información sobre Punto y cómo usarlo en XAML o en código, consulta el tema de referencia de la API para Punto.

Línea

Una línea es simplemente una línea trazada entre dos puntos en el espacio de coordenadas. Una Línea ignora cualquier valor proporcionado para Relleno, porque no tiene espacio interior. Para una línea, asegúrese de especificar valores para las propiedades Trazo y Grosor de trazo, porque de lo contrario la línea no se representará.

No use valores de Punto para especificar la forma de una Línea, en su lugar use valores Dobles discretos para X1, Y1, X2 e Y2. Esto permite un marcado mínimo para líneas horizontales o verticales. Por ejemplo, <Line Stroke="Red" X2="400"/> define una línea horizontal de 400 píxeles de longitud. Las otras propiedades X,Y son 0 de manera predeterminada, por lo que en términos de puntos este XAML dibujaría una línea desde (0,0) hasta (400,0). A continuación, puede usar TranslateTransform para mover toda la línea, si desea que comience en un punto distinto de (0,0).

<Line Stroke="Red" X2="400"/>
var line1 = new Line();
line1.Stroke = new SolidColorBrush(Colors.Red);
line1.X2 = 400;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(line1);

Polilínea

Una Polilínea es similar a un Polígono en que el límite de la forma está definido por un conjunto de puntos, excepto que el último punto en una Polilínea no está conectado al primer punto.

Nota:

Puede tener explícitamente un punto inicial y un punto final idénticos en el conjunto de Puntos para la Polilínea, pero en ese caso probablemente podría haber usado un Polígono en su lugar.

Si especifica un Relleno de una Polilínea, el Relleno pinta el espacio interior de la forma, incluso si el punto inicial y el punto final del conjunto de Puntos para la Polilínea no se cruzan. Si no especifica un Relleno, la Polilínea es similar a lo que se habría presentado si hubiera especificado varios elementos de Línea individuales en los que los puntos inicial y final de líneas consecutivas se intersecaran.

Al igual que con un Polígono, la propiedad Puntos define la colección de puntos que conforman el límite. En XAML, los puntos se definen con una lista separada por comas. En el código subyacente, se usa una PointCollection para definir los puntos y se agrega cada punto individual como una estructura de Punto a la colección.

Este ejemplo crea una polilínea con cuatro puntos establecidos en (10,200), (60,140), (130,140) y (180,200). Se define un Trazo pero no un Relleno.

<Polyline Stroke="Black"
          StrokeThickness="4"
          Points="10,200,60,140,130,140,180,200" />
var polyline1 = new Polyline();
polyline1.Stroke = new SolidColorBrush(Colors.Black);
polyline1.StrokeThickness = 4;

var points = new PointCollection();
points.Add(new Windows.Foundation.Point(10, 200));
points.Add(new Windows.Foundation.Point(60, 140));
points.Add(new Windows.Foundation.Point(130, 140));
points.Add(new Windows.Foundation.Point(180, 200));
polyline1.Points = points;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot>
layoutRoot.Children.Add(polyline1);

Aquí está la polilínea representada. Observe que el primer y el último punto no están conectados por el trazo como lo están en un polígono.

A rendered Polyline.

Path

Un trazado es la forma más versátil porque se puede usar para definir una geometría arbitraria. Pero con esta versatilidad viene la complejidad. Veamos ahora cómo crear un trazado básico en XAML.

La geometría de una ruta se define con la propiedad Data. Hay dos técnicas para establecer Data:

  • Puede establecer un valor de cadena para Data en XAML. De esta forma, el valor Path.Data consume un formato de serialización para gráficos. Normalmente no se edita este valor en forma de cadena después de establecerlo por primera vez. En su lugar, se usan herramientas de diseño que le permiten trabajar en una metáfora de diseño o dibujo sobre una superficie. Después, guarde o exporte el resultado y obtendrá un archivo XAML o un fragmento de cadena XAML con la información Path.Data.
  • Puedes establecer la propiedad Data a un único objeto de Geometría. Esto se puede hacer en código o en XAML. Esa única Geometría es típicamente un GeometryGroup, que actúa como un contenedor que puede componer múltiples definiciones de geometría en un único objeto para propósitos del modelo de objetos. La razón más común para hacer esto es porque se quiere usar una o más de las curvas y formas complejas que pueden ser definidas como valores de Segmentos para un PathFigure, por ejemplo BezierSegment.

Este ejemplo muestra un trazado que podría haber resultado del uso de Blend para Visual Studio para producir solo unas pocas formas vectoriales y luego guardar el resultado como XAML. El trazado total consiste en un segmento de curva Bézier y un segmento de línea. El ejemplo pretende principalmente dar algunos ejemplos de qué elementos existen en el formato de serialización Path.Data y qué representan los números.

Este Data comienza con el comando mover, indicado por "M", que establece un punto inicial absoluto para el trazado.

El primer segmento es una curva Bézier cúbica que comienza en (100,200) y termina en (400,175), que se dibuja con los dos puntos de control (100,25) y (400,350). Este segmento se indica mediante el comando "C" en la cadena de atributos Data.

El segundo segmento comienza con un comando de línea horizontal absoluta "H", que especifica una línea trazada desde el punto de conexión del subtrazado anterior (400,175) hasta un nuevo punto de conexión (280,175). Como se trata de un comando de línea horizontal, el valor especificado es una coordenada x.

<Path Stroke="DarkGoldenRod" 
      StrokeThickness="3"
      Data="M 100,200 C 100,25 400,350 400,175 H 280" />

Este es el trazado representado.

Screenshot of a simple rendered path.

El siguiente ejemplo muestra un uso de la otra técnica que comentamos: un GeometryGroup con un PathGeometry. Este ejemplo ejercita algunos de los tipos de geometría contribuyentes que se pueden usar como parte de una PathGeometry: PathFigure y los diversos elementos que pueden ser un segmento en PathFigure.Segments.

<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
    <Path.Data>
        <GeometryGroup>
            <RectangleGeometry Rect="50,5 100,10" />
            <RectangleGeometry Rect="5,5 95,180" />
            <EllipseGeometry Center="100, 100" RadiusX="20" RadiusY="30"/>
            <RectangleGeometry Rect="50,175 100,10" />
            <PathGeometry>
                <PathGeometry.Figures>
                    <PathFigureCollection>
                        <PathFigure IsClosed="true" StartPoint="50,50">
                            <PathFigure.Segments>
                                <PathSegmentCollection>
                                    <BezierSegment Point1="75,300" Point2="125,100" Point3="150,50"/>
                                    <BezierSegment Point1="125,300" Point2="75,100"  Point3="50,50"/>
                                </PathSegmentCollection>
                            </PathFigure.Segments>
                        </PathFigure>
                    </PathFigureCollection>
                </PathGeometry.Figures>
            </PathGeometry>
        </GeometryGroup>
    </Path.Data>
</Path>
var path1 = new Microsoft.UI.Xaml.Shapes.Path();
path1.Fill = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 204, 204, 255));
path1.Stroke = new SolidColorBrush(Colors.Black);
path1.StrokeThickness = 1;

var geometryGroup1 = new GeometryGroup();
var rectangleGeometry1 = new RectangleGeometry();
rectangleGeometry1.Rect = new Rect(50, 5, 100, 10);
var rectangleGeometry2 = new RectangleGeometry();
rectangleGeometry2.Rect = new Rect(5, 5, 95, 180);
geometryGroup1.Children.Add(rectangleGeometry1);
geometryGroup1.Children.Add(rectangleGeometry2);

var ellipseGeometry1 = new EllipseGeometry();
ellipseGeometry1.Center = new Point(100, 100);
ellipseGeometry1.RadiusX = 20;
ellipseGeometry1.RadiusY = 30;
geometryGroup1.Children.Add(ellipseGeometry1);

var pathGeometry1 = new PathGeometry();
var pathFigureCollection1 = new PathFigureCollection();
var pathFigure1 = new PathFigure();
pathFigure1.IsClosed = true;
pathFigure1.StartPoint = new Windows.Foundation.Point(50, 50);
pathFigureCollection1.Add(pathFigure1);
pathGeometry1.Figures = pathFigureCollection1;

var pathSegmentCollection1 = new PathSegmentCollection();
var pathSegment1 = new BezierSegment();
pathSegment1.Point1 = new Point(75, 300);
pathSegment1.Point2 = new Point(125, 100);
pathSegment1.Point3 = new Point(150, 50);
pathSegmentCollection1.Add(pathSegment1);

var pathSegment2 = new BezierSegment();
pathSegment2.Point1 = new Point(125, 300);
pathSegment2.Point2 = new Point(75, 100);
pathSegment2.Point3 = new Point(50, 50);
pathSegmentCollection1.Add(pathSegment2);
pathFigure1.Segments = pathSegmentCollection1;

geometryGroup1.Children.Add(pathGeometry1);
path1.Data = geometryGroup1;

// When you create a XAML element in code, you have to add
// it to the XAML visual tree. This example assumes you have
// a panel named 'layoutRoot' in your XAML file, like this:
// <Grid x:Name="layoutRoot">
layoutRoot.Children.Add(path1);

Este es el trazado representado.

Screenshot of a complex rendered path.

Usar PathGeometry puede ser más legible que rellenar una cadena Path.Data. Por otro lado, Path.Data usa una sintaxis compatible con las definiciones de ruta de imagen de Scalable Vector Graphics (SVG), por lo que puede ser útil para portar gráficos desde SVG, o como salida de una herramienta como Blend.