Diálogos y ventanas

El esqueleto de diálogo de Dinaup, los diálogos predefinidos (confirmar, elegir ficha, histórico, anotaciones) y el gestor de ventanas flotantes.

Todos los diálogos de Dinaup comparten el mismo esqueleto. Se monta con DnzDialogLayout, que tiene tres huecos: cabecera, cuerpo y pie. Los casos repetidos ya están hechos: DnzConfirmDialog pide confirmación antes de borrar, DnzItemPickerDialog abre la selección de una ficha de un informe y DnzHistoricoDialog muestra el histórico de un campo. Cuando el contenido tiene que convivir con la pantalla en lugar de cubrirla, se abre en una ventana flotante con DnzWindowManagerService.

Los diálogos funcionan sobre el DialogService de Radzen: necesitas <RadzenDialog /> en el diseño principal (MainLayout.razor) y @inject DialogService DialogService. Las ventanas necesitan <DnzWindowHost /> en el mismo diseño y el servicio DnzWindowManagerService que registra AddDinaZen.

Qué componente uso

NecesitasComponente
Montar tu propio diálogo con cabecera, cuerpo y pieDnzDialogLayout
Confirmar una acción antes de ejecutarlaDnzConfirmDialog
Que el usuario elija una fila de un informeDnzItemPickerDialog
Ver el histórico de cambios de un campoDnzHistoricoDialog
Comentarios, ficheros o galería de un registroDnzAnnotationsDialog
Qué registros apuntan a esteDnzBacklinksDialog
Vista previa de un correo con botón de enviarDnzEmailPreviewDialog
Editar una imagen en un diálogoDnzImageEditorDialog, en Entrada de datos y formularios
Abrir un informe, una ficha o un documento en ventana flotanteDnzWindowManagerService, OpenAsWindow

El patrón de diálogo Dinaup

Envuelve en DnzDialogLayout

El componente raíz de tu .razor es <DnzDialogLayout>. Aporta la rejilla de tres filas (cabecera, cuerpo con desplazamiento, pie) y el cierre.

Título en TitleContent

Un <span> con el título, normalmente condicional según si creas o editas: @(ID.IsEmpty() ? "Nueva retención" : "Editar retención").

Formulario en BodyContent

Los campos. Mientras cargan los datos, IsLoading=true muestra un esqueleto; con datos, el formulario dentro de un <div class="d-flex flex-column gap-3 p-3">.

Botones en FooterContent

A la derecha: Guardar (Success, IsBusy=@isBusy) y Cancelar (Danger, Text). Si el guardado termina bien, cierra con DialogService.Close(true).

Para confirmar algo destructivo (borrar, dar de baja, desvincular) no montes un DnzDialogLayout. Usa DnzConfirmDialog.OpenAsync, que ya trae el icono, el color según gravedad y los dos botones.


DnzDialogLayout

Estructura fija de cualquier diálogo: cabecera, cuerpo con desplazamiento y pie. Rellenas los tres RenderFragment y el componente pone la rejilla y el botón de cerrar. Solo el cuerpo se desplaza; cabecera y pie quedan fijos.

<DnzDialogLayout AltoAuto=true IsLoading=@cargando ErrorText=@errorCarga OnRetry=@CargarAsync>
    <TitleContent>
        <span>@(ID.IsEmpty() ? "Nueva retención" : "Editar retención")</span>
    </TitleContent>
    <BodyContent>
        <div class="d-flex flex-column gap-3 p-3">
            <RadzenFormField class="w-100" Text="Nombre" Variant="Variant.Flat">
                <RadzenTextBox @bind-Value=@editandoNombre Style="width:100%" />
            </RadzenFormField>
            <RadzenFormField class="w-100" Text="Retención %" Variant="Variant.Flat">
                <RadzenNumeric @bind-Value=@editandoRetencion Style="width:100%" Format="N2" />
            </RadzenFormField>
        </div>
    </BodyContent>
    <FooterContent>
        <div class="d-flex justify-content-end gap-2 align-items-center px-3">
            <RadzenButton Click=@Cerrar ButtonStyle="ButtonStyle.Danger" Text="Cancelar" Variant="Variant.Text" />
            <RadzenButton Click=@GuardarAsync ButtonStyle="ButtonStyle.Success" Text="Guardar" Icon="check" IsBusy=@isBusy />
        </div>
    </FooterContent>
</DnzDialogLayout>

Si omites TitleContent o FooterContent, el componente genera una cabecera automática (Title, Icon y botón de cerrar) y un pie con un botón Cerrar. El patrón Dinaup es darle tu propio pie con Guardar y Cancelar.

Cabecera: automática o a medida

<DnzDialogLayout Title="Detalle del pedido" Icon="receipt" AltoAuto=true>
    <BodyContent>
        <DetallePedidoU Pedido=@pedido />
    </BodyContent>
</DnzDialogLayout>
<DnzDialogLayout AltoAuto=true>
    <TitleContent>
        <div class="d-flex align-items-center justify-content-between w-100">
            <span class="fw-semibold">Detalle del pedido 1042</span>
            <DnzBadgetAutoColor Value="Pagado" />
        </div>
    </TitleContent>
    <BodyContent>
        <DetallePedidoU Pedido=@pedido />
    </BodyContent>
</DnzDialogLayout>

Convención del pie

BotónEstiloVariante
CancelarDangerText
AceptarSuccesssólido, con IsBusy mientras procesa

Marca el botón de aceptar con IsBusy=@variable durante la operación: el botón muestra el indicador de carga mientras dura. Para evitar un doble guardado, comprueba al inicio del método si ya hay uno en curso.

Parámetros

ParámetroTipoPor defectoQué hace
TitlestringnullTexto del título cuando no pasas TitleContent.
IconstringnullIcono Material de la cabecera automática.
TitleContentRenderFragmentnullCabecera a medida.
BodyContentRenderFragmentnullCuerpo del diálogo. Es la única zona con desplazamiento.
FooterContentRenderFragmentnullPie a medida con los botones. Sin él, aparece un botón Cerrar.
ContentStylestringnullstyle en línea para el contenedor del cuerpo.
HeaderStylestringnullstyle en línea para la cabecera.
IsLoadingboolfalseSustituye el cuerpo por un esqueleto de carga.
LoadingTextstringnullCon texto, muestra un indicador de carga con ese mensaje en lugar del esqueleto. Para esperas largas.
ErrorTextstringnullCon texto, sustituye el cuerpo por un aviso de error. Tiene prioridad sobre IsLoading.
OnRetryEventCallbackCon delegado, el aviso de error muestra el botón Reintentar. Un error de configuración se deja sin delegado.
FlatboolfalseDiálogo blanco y plano, sin fondo gris ni degradados.
AltoAutoboolfalseEl diálogo se ajusta a su contenido hasta un máximo de 85vh (variable CSS --dnz-dialog-alto-max), con el cuerpo desplazable y el pie visible. Ábrelo con Height = null en las DialogOptions.

AltoAuto=true es la forma recomendada de abrir un diálogo. No lo uses cuando el cuerpo se dimensiona a partir del hueco (un TPV a pantalla completa, un lienzo, un visor): ahí el alto lo ponen las DialogOptions.

Por @ref tienes SetWidth(string width) y SetHeight(string height) para ajustar el tamaño desde código, y Cerrar(), que llama a DialogService.Close().


DnzConfirmDialog

Diálogo de confirmación para acciones que no se pueden deshacer. No va en tu marcado: se abre con el método estático OpenAsync, que devuelve true si el usuario confirma y false si cancela o cierra.

@code {
    async Task EliminarDominioAsync(string dominio)
    {
        if (await DnzConfirmDialog.OpenAsync(DialogService, $"Se elimina el dominio '{dominio}' y no se puede deshacer.", "Eliminar dominio", DnzConfirmSeverity.Danger, "Eliminar") == false) return;

        await BorrarDominioAsync(dominio);
        NotificationService.Notify(NotificationSeverity.Success, "Dominio eliminado");
    }
}

El patrón es siempre el mismo: if (await DnzConfirmDialog.OpenAsync(...) == false) return; y después la acción.

Firma de OpenAsync

public static async Task<bool> OpenAsync(DialogService dialogService, string message, string title = "Confirmar", DnzConfirmSeverity severity = DnzConfirmSeverity.Warning, string okText = "Confirmar", string cancelText = "Cancelar")
ArgumentoTipoPor defectoQué hace
dialogServiceDialogServiceEl servicio de diálogos de Radzen inyectado.
messagestringTexto que explica la consecuencia.
titlestring"Confirmar"Título de la cabecera.
severityDnzConfirmSeverityWarningColor, icono y estilo del botón de aceptar según gravedad.
okTextstring"Confirmar"Texto del botón de aceptar, con el verbo de la acción: "Eliminar", "Dar de baja".
cancelTextstring"Cancelar"Texto del botón de cancelar.

Valores de DnzConfirmSeverity

ValorCuándo usarlo
InfoAviso informativo, sin riesgo.
WarningAcción que conviene revisar (valor por defecto).
DangerAcción destructiva o irreversible (borrar, anular). El botón se muestra en rojo.
SuccessConfirmación positiva de algo que va a completarse.
if (await DnzConfirmDialog.OpenAsync(DialogService, $"Se elimina el dominio '{dominio}' y no se puede deshacer.", "Eliminar dominio", DnzConfirmSeverity.Danger, "Eliminar") == false) return;
if (await DnzConfirmDialog.OpenAsync(DialogService, "La app externa tendrá acceso a tus datos.", "Instalar app externa", DnzConfirmSeverity.Warning, "Instalar") == false) return;

DnzItemPickerDialog

Selector de ficha sobre un informe. Abre un diálogo con un DnzReportView de 50 filas por página y devuelve la fila que elija el usuario, o null si cancela.

@code {
    async Task ElegirClienteAsync()
    {
        var fila = await DnzItemPickerDialog.OpenAsync(DialogService, Client, SectionsD.EntidadesD._SectionID);
        if (fila == null) return;

        var nombre = fila.GetLegible("nombre");
    }
}

Firma de OpenAsync

public static async Task<DinaupDynamicRowDTO> OpenAsync(DialogService dialogService, DinaupClientC client, string reportId)
ArgumentoTipoQué hace
dialogServiceDialogServiceEl servicio de diálogos de Radzen inyectado.
clientDinaupClientCEl cliente con el que se ejecuta el informe.
reportIdstringGUID del informe que alimenta la tabla de selección.

El diálogo mide min(95%, 1806px) de ancho y 600 px de alto, y es redimensionable y arrastrable. Para el selector embebido en un formulario, consulta Selectores.


DnzHistoricoDialog

Histórico de cambios de un campo de un registro (auditoría): primero el valor vigente con autor y fecha, después cada cambio como "antes y después". Lee Client.GetHistoryChanges.

@code {
    async Task VerHistorialAsync()
    {
        await DnzHistoricoDialog.Open(DialogService, Client, sectionId, rowId, "importe", "Importe");
    }
}
public static async Task Open(DialogService dialogService, DinaupClientC client, Guid sectionId, Guid rowId, string field, string fieldLabel = null)

fieldLabel es el nombre visible del campo; sin él se muestra el nombre interno.


DnzAnnotationsDialog

Comentarios, ficheros y galería pública de un registro, con pestañas para cambiar de tipo y un redactor con menciones. Lee Client.Annotations_GetAsync y escribe con Client.Annotation_PutAsync.

public static async Task OpenAsync(DialogService dialogService, DinaupClientC client, Guid sectionId, Guid rowId, AnnotationTypeE type)

AnnotationTypeE tiene Comments, Files y PublicGallery. DnzFormView pide abrir este diálogo con OnAnnotationOpen, que recibe un DinaupAnnotationRequest con SectionId, RowId, Type y Client.


DnzBacklinksDialog

Lista los registros que apuntan al registro dado, una línea por campo de relación. Lee Client.GetBacklinksAsync al abrir.

public static async Task OpenAsync(DialogService ds, DinaupClientC client, string sectionId, string rowId)

DnzEmailPreviewDialog

DnzEmailPreview envuelto en un diálogo con un botón de confirmar. Devuelve true si el usuario pulsa el botón.

public static async Task<bool> OpenAsync(DialogService dialogService, string body, string destinatario, string asunto, EventCallback onConfirmar, string confirmarTexto = "Enviar")

Otros diálogos

DiálogoQué hace
DnzFormInspectorDialogInspector de los campos de una ficha (RowId, SectionId, SectionLabel, Fields: List<VirtualFormDTO.Control>). Herramienta de depuración de DnzFormView.
DnzReportDebugDialogTiempos y metadatos de un informe. Detalle en Tablas e informes.
DnzRecoveryPasswordWithDinaupDialogPanel informativo de recuperación de contraseña con la cuenta Dinaup. Sin parámetros; se abre con DialogService.OpenAsync<DnzRecoveryPasswordWithDinaupDialog>.

Ventanas flotantes

Una ventana flotante es un panel arrastrable, redimensionable, minimizable y maximizable que convive con la pantalla. Las gestiona DnzWindowManagerService (registrado por AddDinaZen, uno por circuito Blazor) y las muestra DnzWindowHost, que además monta la barra de tareas DnzTaskbar cuando hay alguna abierta.

Coloca DnzWindowHost en el diseño principal

Una vez, en MainLayout.razor, junto a <RadzenDialog />.

<DinaZen.Components.WindowManager.DnzWindowHost />

Abre con los métodos OpenAsWindow

DnzReportView, DnzFormView y DnzDynamicDocumentView traen un OpenAsWindow que recibe el gestor y devuelve el id de la ventana.

@inject DnzWindowManagerService WindowManager

@code {
    private void AbrirCliente(string clienteId)
    {
        var windowId = DnzFormView.OpenAsWindow(WindowManager, Client, SectionsD.EntidadesD._SectionID, clienteId, title: "Cliente");
    }
}

O abre tu propio contenido con Open

Open(WindowOptions options, RenderFragment content) acepta cualquier RenderFragment.

@code {
    private void AbrirTarea(string tareaId, string titulo)
    {
        RenderFragment content = builder =>
        {
            builder.OpenComponent<TareaU>(0);
            builder.AddAttribute(1, "TareaId", tareaId);
            builder.CloseComponent();
        };
        WindowManager.Open(new WindowOptions { Title = titulo, Icon = "task_alt", InitialWidth = 1100, InitialHeight = 700, MinWidth = 800, MinHeight = 400 }, content);
    }
}

WindowOptions

PropiedadTipoPor defectoQué hace
Titlestring""Título de la ventana.
Subtitlestring""Subtítulo.
Iconstring""Icono Material.
IconUrlstring""URL de icono propio. Tiene prioridad sobre Icon.
InitialWidthdouble1100Ancho inicial en px.
InitialHeightdouble620Alto inicial en px.
MinWidthdouble1000Ancho mínimo al redimensionar. Redúcelo si el contenido tiene una maqueta estrecha.
MinHeightdouble400Alto mínimo al redimensionar.
ControlKeystringnullIdentidad de la ventana a efectos de "de esta solo puede haber una". Con valor, volver a pedirla trae al frente la que ya hay y Open devuelve su id.
AmbitoAmbitoVentanaEPestanaHasta dónde llega ese control: Pestana, Usuario o Licencia. Los dos últimos necesitan que la app conteste por ResolverOcupadaFuera.
PresetIdstringnullId predefinido, para pasar el WindowId al contenido antes de abrir.

DnzWindowManagerService

MiembroQué hace
WindowsLas ventanas abiertas (IReadOnlyList<WindowState>).
MaxWindowsConstante: 6 ventanas a la vez. Al superarlo, Open no abre y dispara OnMaxWindowsReached.
Open(options, content)Abre y devuelve el id. Devuelve null si otra sesión tiene la ventana con ese ControlKey (OnVentanaOcupada).
Close(windowId), CloseActive()Cierra una ventana o la activa.
Focus, Minimize, Restore, ToggleMaximize, ToggleMinimizeCon el windowId.
UpdateTitle, UpdateSubtitle, UpdateIcon, UpdateWindowInfoCambian los rótulos de una ventana abierta.
GetWindow(windowId)El WindowState (id, título, posición, tamaño, IsMinimized, IsMaximized, IsActive, ControlKey).
OnChangedEvento al cambiar cualquier ventana.
ResolverOcupadaFueraFunc<string, AmbitoVentanaE, string> que la app rellena para los ámbitos Usuario y Licencia.
ControlKeysAbiertasLas ControlKey abiertas en este circuito, para publicarlas a otras sesiones.

Las ventanas se renderizan sueltas, sin contenedor, para que cada una compita en z-index con los modales de Radzen. Cambiar de capa solo cambia un número, no el contenedor, así que lo escrito dentro no se pierde.


Relacionado

En esta página