AstrolUI AstrolUI
Amarillo Naranja Rojo Verde Azul Morado Color propio
Modo claro
AstrolBoard

AstrolBoard<TBox, TRelation> pinta un Board<TBox, TRelation>: una lista de cajas (Box<TBox>, con su contenido de tipo TBox) y de relaciones entre ellas (Relation<TRelation>). Usted decide cómo se ve cada caja con BoxRender; el tablero modifica la posición y el tamaño en el mismo objeto y avisa con OnChangeBoard para que lo guarde.

1. Ejemplo mínimo

100%
Design
Ana
Build
Luis
Test
Eva

Move a box.

@using Astrol.Component.Board
@using Astrol.Entities.Basic.UI.Board
 
@* Drag the boxes, resize them from the corners, Ctrl+click to select several, arrows to move them,
 Ctrl+Z / Ctrl+Y, Ctrl+F to search and Ctrl+wheel to zoom. *@
<div style="height:380px">
 <AstrolBoard TBox="WorkItem" TRelation="object"
 Board="board"
 BackgroundType="BackgroundType.Points"
 MinWidth="80"
 MinHeight="60"
 OnChangeBoard="OnChange">
 <BoxRender>
 <div style="height:100%; padding:8px; border-radius:8px; border:1px solid var(--color-gray-a); background:var(--layout-menu-bg); overflow:hidden">
 <b>@context.BOX_NAME</b>
 <div style="font-size:.8em; opacity:.8">@context.Content.Owner</div>
 </div>
 </BoxRender>
 </AstrolBoard>
</div>
<p aria-live="polite">@message</p>
 
@code {
 private string message = "Move a box.";
 private readonly Board<WorkItem, object> board = Create();
 
 private void OnChange(Board<WorkItem, object> changed)
 {
 var first = changed.Boxes[0];
 message = $"{first.BOX_NAME}: X {first.X:0}, Y {first.Y:0}, {first.Width:0} x {first.Height:0}";
 }
 
 private static Board<WorkItem, object> Create()
 {
 var b = new Board<WorkItem, object>();
 b.Boxes.Add(new Box<WorkItem> { X = 30, Y = 40, Width = 160, Height = 90, BOX_NAME = "Design", Content = new WorkItem { Owner = "Ana" } });
 b.Boxes.Add(new Box<WorkItem> { X = 290, Y = 40, Width = 160, Height = 90, BOX_NAME = "Build", Content = new WorkItem { Owner = "Luis" } });
 b.Boxes.Add(new Box<WorkItem> { X = 290, Y = 220, Width = 160, Height = 90, BOX_NAME = "Test", Content = new WorkItem { Owner = "Eva" } });
 b.Relations.Add(new Relation<object> { BoxIdA = b.Boxes[0].Id, BoxIdB = b.Boxes[1].Id });
 b.Relations.Add(new Relation<object> { BoxIdA = b.Boxes[1].Id, BoxIdB = b.Boxes[2].Id });
 return b;
 }
 
 public class WorkItem
 {
 public string Owner { get; set; } = string.Empty;
 }
}

2. Modelo de datos

  • Board<TBox, TRelation>: Id (string), Scale (float, zoom; 1 = 100 %), Boxes (List<Box<TBox>>) y Relations (List<Relation<TRelation>>).
  • Box<TBox>: Id (string, único), BOX_NAME (texto que usa la búsqueda), X, Y, Width, Height (float, en unidades del tablero sin zoom), Color (color en el mini-mapa), Content (TBox, sus datos) y ShowGuides (bool?, guías solo para esta caja).
  • Relation<TRelation>: BoxIdA y BoxIdB (cajas que une; si son la misma se dibuja un bucle en su esquina inferior derecha), Vertices (List<Vertex>, puntos intermedios), AnchorA / AnchorB (BoxAnchor?, punto fijo de cada caja donde empieza y termina la línea), Arrow (RelationArrow?), Label (string) y Content (TRelation).
  • Vertex: Id, X, Y y Order (la línea pasa por los vértices en ese orden).
  • Position: X, Y (la reciben los eventos de los menús, en píxeles del tablero; divida entre Scale para pasar a unidades del modelo).
  • Para añadir o quitar cajas o relaciones, modifique las listas del mismo objeto Board: el tablero lo detecta al volver a pintarse y lo añade al historial. Si asigna otro objeto Board, el tablero empieza de cero (selección e historial).

3. Ratón y teclado

  • Arrastrar una caja: la mueve (y a todas las seleccionadas, con los vértices de las relaciones entre ellas).
  • Arrastrar una esquina o un lado de la caja: la redimensiona, respetando MinWidth / MinHeight.
  • Arrastrar un vértice: dobla la relación. Arrastrar el fondo: desplaza el tablero.
  • Arrastrar el extremo de una línea (con EditAnchors): lo fija en un punto del borde de la caja; doble clic lo libera.
  • Ctrl + clic: añade o quita una caja de la selección. Ctrl + A: selecciona todo. Esc: limpia la selección y cierra menús.
  • Flechas: mueven la selección 5 px (Mayús + flechas: 20 px). Supr: invoca OnKeyDownDelete o, si no se usa, borra la selección y sus relaciones.
  • Ctrl + C / V / D: copiar, pegar y duplicar. Arrastrar un puerto (AllowConnect): conectar. Mayús + arrastrar el fondo: seleccionar con un rectángulo.
  • Ctrl + Z: deshacer. Ctrl + Y o Ctrl + Mayús + Z: rehacer.
  • Ctrl + F: buscar cajas por BOX_NAME (sin distinguir mayúsculas ni acentos). Ctrl + rueda: zoom (20 % a 300 %).
  • Clic derecho: menú del tablero, de la caja, de la relación o del vértice (si se activan).
  • Los atajos solo actúan con el foco dentro del tablero; el clic derecho y los atajos del navegador siguen funcionando en el resto de la página, y no actúan mientras se escribe en un campo dentro de una caja.

4. Menús contextuales

  • Active cada menú con ShowBoardContextMenu, ShowBoxContextMenu, ShowRelationContextMenu o ShowVertexContextMenu y escriba su contenido en BoardContextMenu, BoxContextMenu, RelationContextMenu o VertexContextMenu.
  • Al abrirse, OnOpen…ContextMenu le da el id de la caja, de la relación (BoxIdA, BoxIdB) o del vértice y la posición del clic en píxeles del tablero (divida entre Scale para insertar ahí una caja). Guárdelos y úselos en las opciones.
  • Dentro del tablero el menú del navegador no se abre: solo aparece el del tablero, la caja, la línea o el vértice.
  • El menú se cierra al elegir una opción, con Esc o al hacer clic fuera, y se coloca para que quepa en la parte visible del tablero.
  • MenuOpion: opción lista para usar con Icon, IconColor e IconBackground (opcionales; sin ellos el icono es neutro), Text, KeyboardShortcut, HasSeparator, Status (false = desactivada) y OnClick. Use NeedAuthorize="false" si no filtra por roles (Roles). También puede usar su propio HTML con la clase p-board-menu-option.
<AstrolBoard TBox="Task" TRelation="object" Board="board"
 ShowBoardContextMenu="true" ShowBoxContextMenu="true"
 OnOpenBoardContextMenu="p => { position = p; }"
 OnOpenBoxContextMenu="e => { (boxId, position) = e; }">
 <BoardContextMenu>
 <MenuOpion NeedAuthorize="false" Icon="astrol-plus" Text="New box here" OnClick="AddBox" />
 </BoardContextMenu>
 <BoxContextMenu>
 <MenuOpion NeedAuthorize="false" Icon="astrol-clone" Text="Duplicate" OnClick="Duplicate" HasSeparator="true" />
 <MenuOpion NeedAuthorize="false" Icon="astrol-trash" IconColor="var(--aui-danger)" Text="Delete" OnClick="Delete" />
 </BoxContextMenu>
 ...
</AstrolBoard>
private Position position = new();
private string? boxId;
 
// The position is in board pixels: divide by Scale to get model units
private void AddBox() => board.Boxes.Add(new Box<Task> { X = position.X / board.Scale, Y = position.Y / board.Scale, BOX_NAME = "New" });
private void Delete() => board.Boxes.RemoveAll(b => b.Id == boxId);

5. Anclaje de las líneas

  • RelationAnchor (parámetro del tablero): Center (por defecto, las líneas van de centro a centro) o Border (empiezan y terminan en el borde de las cajas, en la dirección del otro extremo o del vértice más cercano).
  • Relation.AnchorA / AnchorB (BoxAnchor, opcional): un punto fijo de cada caja, relativo a ella (X e Y de 0 a 1). Atajos: BoxAnchor.Top, Right, Bottom, Left, Center. Si es null se usa RelationAnchor.
  • LineStyle (parámetro del tablero): Straight (por defecto, tramos rectos) o Curved (curvas suaves que salen perpendiculares al lado de la caja y pasan por los vértices).
  • EditAnchors (parámetro del tablero): al pasar el ratón por una línea o por una caja aparecen los círculos de sus extremos, dibujados encima de la caja (tienen prioridad sobre las asas de redimensionar: si coinciden, mueva primero el extremo); el usuario arrastra el extremo de una línea a un punto del borde (se ajusta a esquinas, cuartos y centros de los lados) y el punto queda en AnchorA / AnchorB; doble clic en el extremo lo libera. Entra en el historial (Ctrl+Z) e invoca OnChangeBoard.
  • Una relación de una caja consigo misma (BoxIdA = BoxIdB) se dibuja como un bucle en su esquina inferior derecha.
<AstrolBoard TBox="Task" TRelation="object" Board="board"
 RelationAnchor="RelationAnchor.Border"
 LineStyle="RelationLineStyle.Curved"
 EditAnchors="true"> ... </AstrolBoard>
// From the middle of the right side of A to the top quarter of the left side of B
board.Relations.Add(new Relation<object>
{
 BoxIdA = a.Id,
 BoxIdB = b.Id,
 AnchorA = BoxAnchor.Right, // (1, 0.5)
 AnchorB = new BoxAnchor(0, 0.25F), // x and y from 0 to 1, relative to the box
});

6. Herramientas de edición

  • Flechas: RelationArrow (parámetro del tablero: None, End A → B, Start A ← B, Both) y Relation.Arrow para una relación. Un extremo con flecha va al borde de la caja aunque el modo sea centro.
  • Etiquetas: Relation.Label (texto) o RelationRender (RenderFragment<Relation>, cualquier contenido), centradas en la mitad del recorrido de la línea o de la curva. Clic derecho en la etiqueta = menú de la relación.
  • Conectar arrastrando: AllowConnect. Al pasar por una caja aparecen cuatro puertos fuera de ella; arrastre uno hasta otra caja (se resalta) y suelte. OnConnect (BoardConnectEventArgs) recibe la relación antes de añadirla para completarla (Content, Label, Arrow…) o cancelarla (Cancel = true).
  • Selección con rectángulo: BackgroundDrag = Pan (por defecto, arrastrar el fondo desplaza y con Mayús selecciona) o Select (al revés). Con Ctrl se suma a la selección.
  • Ajuste: SnapToGrid (tamaño de la cuadrícula, 0 = sin ajuste) al mover y redimensionar, y SnapToBoxes: al mover, la caja se alinea con los bordes y centros de las demás (a menos de 6 px) y se dibuja la guía.
  • Portapapeles (AllowClipboard, por defecto true): Ctrl+C, Ctrl+V (desplazado 20 px cada vez) y Ctrl+D copian las cajas seleccionadas con las relaciones entre ellas; las copias quedan seleccionadas e invocan OnBoxesAdded. El contenido se copia con CloneBox / CloneRelation o, si no se indican, por JSON (si no se puede, se comparte la referencia).
  • Supr: si usa OnKeyDownDelete, decide su código (como antes); si no, se quitan la selección y sus relaciones y se invoca OnBoxesRemoved.
  • Métodos para sus menús: CopySelection(), PasteAsync(), DuplicateSelectionAsync(), DeleteSelectionAsync(). Todo entra en el historial (Ctrl+Z) e invoca OnChangeBoard.
<AstrolBoard TBox="Task" TRelation="object" Board="board" @ref="boardRef"
 RelationAnchor="RelationAnchor.Border"
 RelationArrow="RelationArrow.End"
 AllowConnect="true" OnConnect="Connect"
 BackgroundDrag="BoardBackgroundDrag.Select"
 SnapToGrid="20" SnapToBoxes="true"
 OnBoxesAdded="Added" OnBoxesRemoved="Removed">
 <RelationRender>@context.Label</RelationRender>
 ...
</AstrolBoard>
// The relation is not on the board yet: complete it or cancel it
private void Connect(BoardConnectEventArgs<object> e)
{
 e.Relation.Label = "next";
 e.Cancel = board.Relations.Any(r => r.BoxIdA == e.Relation.BoxIdA && r.BoxIdB == e.Relation.BoxIdB);
}
 
board.Relations.Add(new Relation<object> { BoxIdA = a.Id, BoxIdB = b.Id, Label = "Yes", Arrow = RelationArrow.Both });

7. Orden automático

  • ArrangeAsync(BoardLayoutOptions?) (con @ref) ordena el tablero por capas siguiendo el sentido de las relaciones (de BoxIdA a BoxIdB): rompe los ciclos, reduce los cruces entre líneas y alinea cada caja con sus vecinas. Los grupos no conectados se colocan uno al lado del otro y las cajas sin relaciones, al final.
  • Las relaciones que saltan varias capas reciben vértices para rodear las cajas; las demás quedan rectas (sus vértices se quitan). Con UseVertices = false se quitan todos.
  • No añade, quita ni modifica relaciones (cajas, contenido, anclajes) ni cambia el tamaño de las cajas: solo mueve cajas y vértices. Entra en el historial (Ctrl+Z) e invoca OnChangeBoard.
  • BoardLayoutOptions: Direction (TopToBottom o LeftToRight), LayerSpacing (80), BoxSpacing (40), GroupSpacing (80), UseVertices (true), Left/Top (20, nunca menos que MinLeft/MinTop), MaxWidth (1600, ancho de cada fila de grupos; se limita a MaxRight) y Animate (true).
  • BoardLayout.Arrange(board, options) hace lo mismo sin el componente (por ejemplo, al crear el tablero o en el servidor).
<button @onclick="Arrange">Arrange</button>
<AstrolBoard TBox="Task" TRelation="object" Board="board" @ref="boardRef"> ... </AstrolBoard>
private AstrolBoard<Task, object>? boardRef;
 
private Task Arrange() => boardRef!.ArrangeAsync(new BoardLayoutOptions
{
 Direction = BoardLayoutDirection.LeftToRight,
 LayerSpacing = 60,
});
 
// Without the component (e.g. before showing the board):
BoardLayout.Arrange(board);

8. Guías al arrastrar (ShowGuides)

Con ShowGuides="true", al arrastrar o redimensionar la caja seleccionada aparecen líneas guía discontinuas desde sus esquinas (hacia afuera) y desde el centro de sus lados (más tenues), hasta el borde del tablero, para alinearla con otras cajas. Por defecto están desactivadas. Cada caja puede forzarlas con Box.ShowGuides (true / false; null sigue al tablero). El aspecto se cambia con variables CSS:

<AstrolBoard TBox="Task" TRelation="object" Board="board" ShowGuides="true"> ... </AstrolBoard>
// Per box: null = follow the board, true / false = force it for this box
board.Boxes.Add(new Box<Task> { BOX_NAME = "Title", ShowGuides = false });
/* Optional: line style, separation and opacity of the side lines */
:root {
 --border-line: 1px dashed var(--color-red-transparent);
 --pbse-offset: 2px;
 --pbse-side-opacity: 0.45;
}

9. Rendimiento

  • Arrastrar, redimensionar, mover vértices, desplazar el tablero y el mini-mapa se hacen en el navegador, un pintado por fotograma: no hay llamadas a .NET mientras arrastra (una sola al soltar). En Blazor Server no viaja nada por la red durante el arrastre.
  • Al confirmar un cambio solo se vuelven a pintar las cajas y relaciones que cambiaron.
  • No se inicializa nada por caja: con 500 cajas el tablero se monta en la mitad de tiempo que antes y cada paso de un arrastre pasa de ~360 ms a ~2 ms.
  • El historial guarda solo la geometría (no copia su contenido TBox).
  • Si su BoxRender es costoso, manténgalo simple o use componentes propios: se vuelve a pintar cuando el padre del tablero se vuelve a pintar.

Parámetros que admite

  • Board (Board<TBox, TRelation>), el tablero a pintar (requerido).
  • BoxRender (RenderFragment<Box<TBox>>), contenido de cada caja; context es la caja.
  • BackgroundType (BackgroundType), fondo: None, Grids (por defecto), Points o Quadrants.
  • ShowGuides (bool), guías al arrastrar (por defecto false).
  • ShowSearch, ShowZoom (bool), búsqueda y zoom con mini-mapa (por defecto true); ZoomStatus (bool), mini-mapa abierto; EnableZoom y AllowChangeScale (bool), permiten el zoom con la rueda y con los botones.
  • MinWidth, MinHeight (float), tamaño mínimo de las cajas (por defecto 100); MinLeft, MinTop (float) (por defecto 10), MaxRight, MaxBottom (float), área en la que se pueden mover.
  • RelationAnchor (RelationAnchor), las líneas salen del centro (Center, por defecto) o del borde (Border) de las cajas. LineStyle (RelationLineStyle), líneas rectas (Straight, por defecto) o curvas (Curved). EditAnchors (bool), el usuario fija los extremos arrastrándolos.
  • RelationArrow (RelationArrow), flechas; RelationRender (RenderFragment<Relation>), etiquetas. AllowConnect (bool) y OnConnect (BoardConnectEventArgs), conectar arrastrando. BackgroundDrag (BoardBackgroundDrag), desplazar o seleccionar. SnapToGrid (float), SnapToBoxes (bool), ajuste. AllowClipboard (bool), CloneBox, CloneRelation (Func), OnBoxesAdded, OnBoxesRemoved.
  • DiscontinueLine (bool), relaciones con línea discontinua. BoxDelay (bool), transición suave al mover con el teclado o deshacer.
  • LongMemory (int), estados del historial (por defecto 10; 0 lo desactiva).
  • ShowBoardContextMenu, ShowBoxContextMenu, ShowRelationContextMenu, ShowVertexContextMenu (bool) y BoardContextMenu, BoxContextMenu, RelationContextMenu, VertexContextMenu (RenderFragment), menús de clic derecho (use MenuOpion o la clase p-board-menu-option en las opciones).
  • LoadComponent (RenderFragment), indicador de carga. TextPlaceholderSearch, AriaLabel (string), CssClass, CssStyle (string).
  • OnChangeBoard (EventCallback<Board>), tras cada cambio del usuario (mover, redimensionar, anclar una línea, flechas, deshacer).
  • OnOpenBoardContextMenu (Position), OnOpenBoxContextMenu ((string, Position)), OnOpenRelationContextMenu ((string, string, Position)), OnOpenVertexContextMenu ((string, Position)) y OnChange…ContextMenuStatus (bool).
  • OnDobleClickBox (Box<TBox>), OnKeyDownDelete, OnChangeScale (float), OnChangeZoomStatus (bool).

Métodos públicos

Con @ref (private AstrolBoard<Task, object>? boardRef;):

  • ArrangeAsync(options): ordena todas las cajas según sus relaciones (véase «Orden automático»).
  • CopySelection(), PasteAsync(), DuplicateSelectionAsync(), DeleteSelectionAsync(): portapapeles y borrado de la selección.
  • UndoAsync() / RedoAsync(): deshacer y rehacer.
  • GetHistoryInfo(): CanUndo, CanRedo, CurrentIndex, TotalStates. ClearHistory(): vacía el historial.
  • _idBoxesSelected: ids de las cajas seleccionadas. RemoveItemSelected(ids): las quita de la selección.
  • GetSVGWidth(): ancho del contenido. EndMoveBoxPosition(): recalcula el tamaño y notifica OnChangeBoard.
Markdown para IA © 2026 AstrolUI v10.0.13