true-perfect-code
Version: 1.1.69

P11MessageBox Service

The P11MessageBox is a powerful service component designed to display modal dialogs that halt the execution of calling code until the user interacts with the message box. It's ideal for prompting user decisions (e.g., Yes/No/Cancel), displaying critical information, or collecting user input via custom forms. It integrates with Bootstrap for consistent UI.
Note: The P11MessageBox service requires a TpcMessageBox component to be present in your application's layout (e.g., in MainLayout.razor or App.razor) to render the dialog. This container component manages the display of the active message box.

Required Configuration for P11MessageBox

To use the P11MessageBox service, you must perform a few one-time setup steps in your Blazor application:

1. Register the Service in Program.cs

The IMessageBoxService must be registered in the dependency injection container.


// In Program.cs (Blazor Server or Blazor WebAssembly)
builder.Services.AddScoped<IMessageBoxService, MessageBoxService>();
                
2. Add the Component to your Layout

The P11MessageBox component, which acts as the container, needs to be added to your application's main layout file (e.g., Routes.razor or App.razor).


// In Routes.razor
<Router AppAssembly="typeof(Layout.MainLayout).Assembly">
  <Found Context="routeData">
    <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
    <FocusOnNavigate RouteData="routeData" Selector="h1" />
  </Found>
</Router>

<P11MessageBox />
                
3. Add the Namespace to _Imports.razor

To easily access the component without a full namespace, add the following line:


// In _Imports.razor
@using p11.UI
@using p11.UI.Models
                
4. Inject the Service for Usage

Finally, inject the service into any component where you want to display a message box.


// In your .razor component or its code-behind
@inject IMessageBoxService MessageBoxService
                


Examples: Standard Message Boxes

These examples demonstrate how to use the standard message box methods.

Standard MessageBox Buttons

Results:

Implementation

@using p11.UI.Services
@inject IMessageBoxService MessageBoxService

<h3 class="h4 mb-4">Examples: Standard Message Boxes</h3>
<p>These examples demonstrate how to use the standard message box methods.</p>

<div class="bg-light p-4 rounded mb-4">
    <h4 class="mb-3">Standard MessageBox Buttons</h4>
    <div class="d-flex flex-wrap gap-2 align-items-center">
        <button class="btn btn-primary" @onclick="ShowOkBox">Show OK Message</button>
        <button class="btn btn-secondary" @onclick="ShowYesNoBox">Show Yes/No Message</button>
        <button class="btn btn-info" @onclick="ShowYesNoCancelBox">Show Yes/No/Cancel Message</button>
        <button class="btn btn-warning" @onclick="ShowCustomizedMessage">Customize message buttons</button>
    </div>
    <div class="mt-3">Results:</div>
    <div class="d-flex flex-column gap-2 mt-2">
        <div>@consoleoutput</div>
    </div>
</div>
@code {
    @inject IMessageBoxService MessageBoxService

    private string consoleoutput = "";

    private async Task ShowOkBox()
    {
        consoleoutput = "";
        await Task.Delay(30);
        StateHasChanged();
        MessageBoxResult result = await MessageBoxService.ShowOkAsync(
            title: "Confirmation",
            message: "This is a simple information message with an OK button."
        );

        switch (result)
        {
            case MessageBoxResult.Ok:
                consoleoutput = "User has clicked OK.";
                break;

            case MessageBoxResult.None:
                consoleoutput = "User has left the box without clicking.";
                break;
        }
    }

    private async Task ShowYesNoBox()
    {
        await Task.Delay(30);
        StateHasChanged();
        MessageBoxResult result = await MessageBoxService.ShowYesNoAsync(
            title: "Confirmation",
            message: "Do you really want to proceed?"
        );

        switch (result)
        {
            case MessageBoxResult.Yes:
                consoleoutput = "User has clicked YES.";
                break;
            case MessageBoxResult.No:
                consoleoutput = "User has clicked NO.";
                break;
            case MessageBoxResult.None:
                consoleoutput = "User has left the box without clicking.";
                break;
        }
    }

    private async Task ShowYesNoCancelBox()
    {
        await Task.Delay(30);
        StateHasChanged();
        MessageBoxResult result = await MessageBoxService.ShowYesNoCancelAsync(
            title: "Save changes?",
            message: "You have unsaved changes. Do you want to save them before closing the window?"
        );

        switch (result)
        {
            case MessageBoxResult.Yes:
                consoleoutput = "User has clicked YES.";
                break;
            case MessageBoxResult.No:
                consoleoutput = "User has clicked NO.";
                break;
            case MessageBoxResult.Cancel:
                consoleoutput = "User has clicked CANCEL.";
                break;
            case MessageBoxResult.None:
                consoleoutput = "User has left the box without clicking.";
                break;
        }
    }
}

private async Task ShowCustomizedMessage()
{
    await Task.Delay(30);
    StateHasChanged();
    MessageBoxResult result = await MessageBoxService.ShowYesNoCancelAsync(
        title: "Data migration",
        message: "Select the type of data migration:",
        yestext: "Only selected items",
        notext: "all items",
        canceltext: "Cancel migration"
    );

    switch (result)
    {
        case MessageBoxResult.Yes:
            consoleoutput = "User has clicked 'Only selected items'.";
            break;
        case MessageBoxResult.No:
            consoleoutput = "User has clicked 'all items'.";
            break;
        case MessageBoxResult.Cancel:
            consoleoutput = "User has clicked 'Cancel migration'.";
            break;
        case MessageBoxResult.None:
            consoleoutput = "User has left the box without clicking.";
            break;
    }
}


Examples: Custom Message Boxes (user component)

This example shows how to call a user-defined Razor component and pass easy the corresponding parameters.

Custom MessageBoxes

Results:

Implementation

@using p11.UI.Services
@inject IMessageBoxService MessageBoxService
@using [YOUR_PROJECT].Models @* Don't forget to include your model *@

<h3 class="h4 mb-4">Examples: Custom Message Boxes (user component)</h3>
<p>This example shows how to call a user-defined Razor component and pass easy the corresponding parameters.</p>

<div class="bg-light p-4 rounded mb-4">
    <h4 class="mb-3">Custom MessageBoxes</h4>
    <div class="d-flex flex-wrap gap-2 align-items-center">
        <button class="btn btn-warning" @onclick="() => ShowCustomComponentRenderFragmentBox()">Show Custom Quiz (Component)</button>
    </div>
    <div class="mt-3">Results:</div>
    <div class="d-flex flex-column gap-2 mt-2">
        <div>@consoleoutput</div>
    </div>
</div>
@* @using [YOUR_PROJECT].Models  // Don't forget to include your model *@
@code {
    /// <summary>
    /// Defines the possible actions a user can take within a quiz dialog.
    /// </summary>
    public enum QuizActionType
    {
        /// <summary>
        /// The user confirmed their answer.
        /// </summary>
        Ok,

        /// <summary>
        /// The user chose to skip the current question.
        /// </summary>
        Skip,

        /// <summary>
        /// The user chose to pause the quiz.
        /// </summary>
        Pause,

        /// <summary>
        /// The user chose to quit the quiz entirely.
        /// </summary>
        Quit,

        /// <summary>
        /// No specific action was taken (e.g., dialog closed via Escape or backdrop).
        /// </summary>
        None
    }

    /// <summary>
    /// Represents the result of a user's interaction with the QuizQuestion component.
    /// </summary>
    public class QuizResult
    {
        /// <summary>
        /// Gets or sets the type of action taken by the user (e.g., OK, Skip, Pause, Quit).
        /// </summary>
        public QuizActionType Action { get; set; }

        /// <summary>
        /// Gets or sets the zero-based index of the selected answer, if an answer was chosen.
        /// This will be null if no answer was selected or if the action was not 'Ok'.
        /// </summary>
        public int? SelectedAnswerIndex { get; set; }

        /// <summary>
        /// Gets or sets an optional ID for the question, useful if managing multiple questions.
        /// </summary>
        public string QuestionId { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="QuizResult"/> class.
        /// </summary>
        /// <param name="action">The action type.</param>
        /// <param name="selectedAnswerIndex">The selected answer index (optional).</param>
        /// <param name="questionId">The question ID (optional).</param>
        public QuizResult(QuizActionType action = QuizActionType.None, int? selectedAnswerIndex = null, string questionId = null)
        {
            Action = action;
            SelectedAnswerIndex = selectedAnswerIndex;
            QuestionId = questionId;
        }
    }

    private string consoleoutput = "";
    
    private async Task ShowCustomComponentRenderFragmentBox(bool isComponent = true)
    {
        consoleoutput = "";
        QuizResult? quizResult = null;

        if (isComponent)
        {
            quizResult = await MessageBoxService.ShowAsync<ExampleQuiz, QuizResult>(
                new
                {
                    QuestionText = "Which planet is closest to Earth?",
                    Answers = new List<string> { "Mars", "Venus", "Jupiter" },
                    QuestionId = "Q1"
                },
                title: "Knowledge Test: Question 1"
            );
        }
        else
        {
            quizResult = await MessageBoxService.ShowAsync<QuizResult>(
                (setter) =>
                {
                    return (builder) =>
                    {
                        builder.OpenComponent<ExampleQuiz>(0);
                        builder.AddAttribute(1, "QuestionText", "Which planet is closest to Earth?");
                        builder.AddAttribute(2, "Answers", new List<string> { "Mars", "Venus", "Jupiter" });
                        builder.AddAttribute(3, "OnQuizCompleted", setter);
                        builder.AddAttribute(4, "QuestionId", "Q1");
                        builder.CloseComponent();
                    };
                },
                title: "Knowledge Test: Question 1"
            );
        }

        if(quizResult != null)
        {
            switch (quizResult.Action)
            {
                case QuizActionType.Ok:
                    consoleoutput = "User has confirmed the answer.";
                    if (quizResult.SelectedAnswerIndex.HasValue)
                    {
                        var answers = new List<string> { "Mars", "Venus", "Jupiter" };
                        string selectedAnswer = answers[quizResult.SelectedAnswerIndex.Value];
                        consoleoutput += $" Selected answer (Index {quizResult.SelectedAnswerIndex.Value}): {selectedAnswer}";

                        // Beispiel für Logik: Richtige Antwort ist Venus (Index 1)
                        if (quizResult.SelectedAnswerIndex.Value == 1)
                        {
                            consoleoutput += " Correct answer! Well done.";
                        }
                        else
                        {
                            consoleoutput += " Unfortunately wrong. The correct answer would have been Venus.";
                        }
                    }
                    else
                    {
                        consoleoutput += " Error: OK clicked, but no answer selected.";
                    }
                    break;

                case QuizActionType.Skip:
                    consoleoutput = "User has skipped the question.";
                    break;

                case QuizActionType.Pause:
                    consoleoutput = "User has suspended the quiz.";
                    break;

                case QuizActionType.Quit:
                    consoleoutput = "User has ended the quiz.";
                    break;

                case QuizActionType.None:
                    consoleoutput = "Quiz was closed without explicit action (e.g. by escaping).";
                    break;

                default:
                    consoleoutput = $"Unknown quiz action: {quizResult.Action}";
                    break;
            }
        }
        else
            consoleoutput = "Quiz was closed without explicit action (e.g. by escaping).";

        await Task.Delay(30);
        StateHasChanged();
    }
}
@using [YOUR_PROJECT].Models

@*
    /// <summary>
    /// A custom Blazor component for displaying a single quiz question with multiple choice answers
    /// and control buttons (OK, Skip, Pause, Quit).
    /// This component is designed to be hosted within the TpcMessageBox.
    /// The logic is integrated directly into the Razor file using the @code block.
    /// </summary>
*@
<div>
    <p class="mb-3 fs-5"><strong>@QuestionText</strong></p>

    @if (Answers != null && Answers.Any())
    {
        <div class="form-group">
            @for (int i = 0; i < Answers.Count; i++)
            {
                var currentAnswerIndex = i;
                <div class="form-check mb-2">
                    <input type="radio"
                           id="@($"answerOption{i}")"
                           name="quizAnswer"
                           class="form-check-input"
                           value="@i"
                           @onchange="() => OnAnswerSelected(currentAnswerIndex)"
                           checked="@(_selectedAnswerIndex == i)" />
                    <label class="form-check-label" for="@($"answerOption{i}")">
                        @Answers[i]
                    </label>
                </div>
            }
        </div>
    }
    else
    {
        <p class="text-danger">Keine Antworten verfügbar.</p>
    }

    <div class="d-flex justify-content-end gap-2 mt-4">
        <button type="button" class="btn btn-primary" @onclick="HandleOkClick" disabled="@(!_selectedAnswerIndex.HasValue)">OK</button>
        <button type="button" class="btn btn-secondary" @onclick="HandleSkipClick">Überspringen</button>
        <button type="button" class="btn btn-info" @onclick="HandlePauseClick">Pausieren</button>
        <button type="button" class="btn btn-danger" @onclick="HandleQuitClick">Beenden</button>
    </div>
</div>

@code {
    /// <summary>
    /// The text of the quiz question to display.
    /// </summary>
    [Parameter]
    public string QuestionText { get; set; }

    /// <summary>
    /// A list of possible answers for the quiz question.
    /// </summary>
    [Parameter]
    public List<string> Answers { get; set; }

    /// <summary>
    /// The callback action provided by the hosting TpcMessageBox (via MessageBoxService)
    /// to communicate the quiz result back and close the dialog.
    /// This is crucial for the asynchronous flow.
    /// </summary>
    [Parameter]
    public Action<QuizResult> OnQuizCompleted { get; set; }

    /// <summary>
    /// Optional: An identifier for the specific question.
    /// Useful if managing a series of quiz questions.
    /// </summary>
    [Parameter]
    public string QuestionId { get; set; }

    private int? _selectedAnswerIndex;

    /// <summary>
    /// Called when a radio button answer is selected.
    /// Updates the internal state for the selected answer.
    /// </summary>
    /// <param name="index">The zero-based index of the selected answer.</param>
    private void OnAnswerSelected(int index)
    {
        _selectedAnswerIndex = index;
        // No need to call StateHasChanged here, Blazor handles it for @bind or @onchange
        // Also, the OK button's disabled state will automatically re-evaluate
    }

    /// <summary>
    /// Handles the click event for the "OK" button.
    /// This confirms the selected answer and closes the quiz dialog.
    /// </summary>
    private void HandleOkClick()
    {
        // Only proceed if an answer is selected
        if (_selectedAnswerIndex.HasValue)
        {
            OnQuizCompleted?.Invoke(new QuizResult(QuizActionType.Ok, _selectedAnswerIndex.Value, QuestionId));
        }
    }

    /// <summary>
    /// Handles the click event for the "Überspringen" button.
    /// Closes the quiz dialog with a 'Skip' action.
    /// </summary>
    private void HandleSkipClick()
    {
        OnQuizCompleted?.Invoke(new QuizResult(QuizActionType.Skip, questionId: QuestionId));
    }

    /// <summary>
    /// Handles the click event for the "Pausieren" button.
    /// Closes the quiz dialog with a 'Pause' action.
    /// </summary>
    private void HandlePauseClick()
    {
        OnQuizCompleted?.Invoke(new QuizResult(QuizActionType.Pause, questionId: QuestionId));
    }

    /// <summary>
    /// Handles the click event for the "Beenden" button.
    /// Closes the quiz dialog with a 'Quit' action.
    /// </summary>
    private void HandleQuitClick()
    {
        OnQuizCompleted?.Invoke(new QuizResult(QuizActionType.Quit, questionId: QuestionId));
    }
}
@* Save this Model under QuizResult.cs *@

namespace [YOUR_PROJECT].Models
{
    /// <summary>
    /// Defines the possible actions a user can take within a quiz dialog.
    /// </summary>
    public enum QuizActionType
    {
        /// <summary>
        /// The user confirmed their answer.
        /// </summary>
        Ok,

        /// <summary>
        /// The user chose to skip the current question.
        /// </summary>
        Skip,

        /// <summary>
        /// The user chose to pause the quiz.
        /// </summary>
        Pause,

        /// <summary>
        /// The user chose to quit the quiz entirely.
        /// </summary>
        Quit,

        /// <summary>
        /// No specific action was taken (e.g., dialog closed via Escape or backdrop).
        /// </summary>
        None
    }

    /// <summary>
    /// Represents the result of a user's interaction with the QuizQuestion component.
    /// </summary>
    public class QuizResult
    {
        /// <summary>
        /// Gets or sets the type of action taken by the user (e.g., OK, Skip, Pause, Quit).
        /// </summary>
        public QuizActionType Action { get; set; }

        /// <summary>
        /// Gets or sets the zero-based index of the selected answer, if an answer was chosen.
        /// This will be null if no answer was selected or if the action was not 'Ok'.
        /// </summary>
        public int? SelectedAnswerIndex { get; set; }

        /// <summary>
        /// Gets or sets an optional ID for the question, useful if managing multiple questions.
        /// </summary>
        public string QuestionId { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="QuizResult"/> class.
        /// </summary>
        /// <param name="action">The action type.</param>
        /// <param name="selectedAnswerIndex">The selected answer index (optional).</param>
        /// <param name="questionId">The question ID (optional).</param>
        public QuizResult(QuizActionType action = QuizActionType.None, int? selectedAnswerIndex = null, string questionId = null)
        {
            Action = action;
            SelectedAnswerIndex = selectedAnswerIndex;
            QuestionId = questionId;
        }
    }
}
           


Examples: Custom Message Boxes (render fragments)

This example shows how to call a user-defined Razor component via a render fragment and pass the corresponding parameters (e.g. QuizQuestion QuestionText='...' Answers='...' ... ).

Custom MessageBoxes

Results:

Implementation

@using p11.UI.Services
@inject IMessageBoxService MessageBoxService
@using [YOUR_PROJECT].Models @* Don't forget to include your model *@

<h3 class="h4 mb-4">Examples: Custom Message Boxes (render fragments)</h3>
<p>This example shows how to call a user-defined Razor component via a render fragment and pass the corresponding parameters (e.g. <code> QuizQuestion QuestionText='...' Answers='...' ... </code>).</p>

<div class="bg-light p-4 rounded mb-4">
    <h4 class="mb-3">Custom MessageBoxes</h4>
    <div class="d-flex flex-wrap gap-2 align-items-center">
        <button class="btn btn-success" @onclick="() => ShowCustomComponentRenderFragmentBox(isComponent: false)">Show Custom Quiz (RenderFragment)</button>
    </div>
    <div class="mt-3">Results:</div>
    <div class="d-flex flex-column gap-2 mt-2">
        <div>@consoleoutput</div>
    </div>
</div>
@* @using [YOUR_PROJECT].Models  // Don't forget to include your model *@
@code {
    /// <summary>
    /// Defines the possible actions a user can take within a quiz dialog.
    /// </summary>
    public enum QuizActionType
    {
        /// <summary>
        /// The user confirmed their answer.
        /// </summary>
        Ok,

        /// <summary>
        /// The user chose to skip the current question.
        /// </summary>
        Skip,

        /// <summary>
        /// The user chose to pause the quiz.
        /// </summary>
        Pause,

        /// <summary>
        /// The user chose to quit the quiz entirely.
        /// </summary>
        Quit,

        /// <summary>
        /// No specific action was taken (e.g., dialog closed via Escape or backdrop).
        /// </summary>
        None
    }

    /// <summary>
    /// Represents the result of a user's interaction with the QuizQuestion component.
    /// </summary>
    public class QuizResult
    {
        /// <summary>
        /// Gets or sets the type of action taken by the user (e.g., OK, Skip, Pause, Quit).
        /// </summary>
        public QuizActionType Action { get; set; }

        /// <summary>
        /// Gets or sets the zero-based index of the selected answer, if an answer was chosen.
        /// This will be null if no answer was selected or if the action was not 'Ok'.
        /// </summary>
        public int? SelectedAnswerIndex { get; set; }

        /// <summary>
        /// Gets or sets an optional ID for the question, useful if managing multiple questions.
        /// </summary>
        public string QuestionId { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="QuizResult"/> class.
        /// </summary>
        /// <param name="action">The action type.</param>
        /// <param name="selectedAnswerIndex">The selected answer index (optional).</param>
        /// <param name="questionId">The question ID (optional).</param>
        public QuizResult(QuizActionType action = QuizActionType.None, int? selectedAnswerIndex = null, string questionId = null)
        {
            Action = action;
            SelectedAnswerIndex = selectedAnswerIndex;
            QuestionId = questionId;
        }
    }

    private string consoleoutput = "";
    
    private async Task ShowCustomComponentRenderFragmentBox(bool isComponent = true)
    {
        consoleoutput = "";
        QuizResult? quizResult = null;

        if (isComponent)
        {
            quizResult = await MessageBoxService.ShowAsync<ExampleQuiz, QuizResult>(
                new
                {
                    QuestionText = "Which planet is closest to Earth?",
                    Answers = new List<string> { "Mars", "Venus", "Jupiter" },
                    QuestionId = "Q1"
                },
                title: "Knowledge Test: Question 1"
            );
        }
        else
        {
            quizResult = await MessageBoxService.ShowAsync<QuizResult>(
                (setter) =>
                {
                    return (builder) =>
                    {
                        builder.OpenComponent<ExampleQuiz>(0);
                        builder.AddAttribute(1, "QuestionText", "Which planet is closest to Earth?");
                        builder.AddAttribute(2, "Answers", new List<string> { "Mars", "Venus", "Jupiter" });
                        builder.AddAttribute(3, "OnQuizCompleted", setter);
                        builder.AddAttribute(4, "QuestionId", "Q1");
                        builder.CloseComponent();
                    };
                },
                title: "Knowledge Test: Question 1"
            );
        }

        if(quizResult != null)
        {
            switch (quizResult.Action)
            {
                case QuizActionType.Ok:
                    consoleoutput = "User has confirmed the answer.";
                    if (quizResult.SelectedAnswerIndex.HasValue)
                    {
                        var answers = new List<string> { "Mars", "Venus", "Jupiter" };
                        string selectedAnswer = answers[quizResult.SelectedAnswerIndex.Value];
                        consoleoutput += $" Selected answer (Index {quizResult.SelectedAnswerIndex.Value}): {selectedAnswer}";

                        // Beispiel für Logik: Richtige Antwort ist Venus (Index 1)
                        if (quizResult.SelectedAnswerIndex.Value == 1)
                        {
                            consoleoutput += " Correct answer! Well done.";
                        }
                        else
                        {
                            consoleoutput += " Unfortunately wrong. The correct answer would have been Venus.";
                        }
                    }
                    else
                    {
                        consoleoutput += " Error: OK clicked, but no answer selected.";
                    }
                    break;

                case QuizActionType.Skip:
                    consoleoutput = "User has skipped the question.";
                    break;

                case QuizActionType.Pause:
                    consoleoutput = "User has suspended the quiz.";
                    break;

                case QuizActionType.Quit:
                    consoleoutput = "User has ended the quiz.";
                    break;

                case QuizActionType.None:
                    consoleoutput = "Quiz was closed without explicit action (e.g. by escaping).";
                    break;

                default:
                    consoleoutput = $"Unknown quiz action: {quizResult.Action}";
                    break;
            }
        }
        else
            consoleoutput = "Quiz was closed without explicit action (e.g. by escaping).";

        await Task.Delay(30);
        StateHasChanged();
    }
}
@using [YOUR_PROJECT].Models

@*
    /// <summary>
    /// A custom Blazor component for displaying a single quiz question with multiple choice answers
    /// and control buttons (OK, Skip, Pause, Quit).
    /// This component is designed to be hosted within the TpcMessageBox.
    /// The logic is integrated directly into the Razor file using the @code block.
    /// </summary>
*@
<div>
    <p class="mb-3 fs-5"><strong>@QuestionText</strong></p>

    @if (Answers != null && Answers.Any())
    {
        <div class="form-group">
            @for (int i = 0; i < Answers.Count; i++)
            {
                var currentAnswerIndex = i;
                <div class="form-check mb-2">
                    <input type="radio"
                           id="@($"answerOption{i}")"
                           name="quizAnswer"
                           class="form-check-input"
                           value="@i"
                           @onchange="() => OnAnswerSelected(currentAnswerIndex)"
                           checked="@(_selectedAnswerIndex == i)" />
                    <label class="form-check-label" for="@($"answerOption{i}")">
                        @Answers[i]
                    </label>
                </div>
            }
        </div>
    }
    else
    {
        <p class="text-danger">Keine Antworten verfügbar.</p>
    }

    <div class="d-flex justify-content-end gap-2 mt-4">
        <button type="button" class="btn btn-primary" @onclick="HandleOkClick" disabled="@(!_selectedAnswerIndex.HasValue)">OK</button>
        <button type="button" class="btn btn-secondary" @onclick="HandleSkipClick">Überspringen</button>
        <button type="button" class="btn btn-info" @onclick="HandlePauseClick">Pausieren</button>
        <button type="button" class="btn btn-danger" @onclick="HandleQuitClick">Beenden</button>
    </div>
</div>

@code {
    /// <summary>
    /// The text of the quiz question to display.
    /// </summary>
    [Parameter]
    public string QuestionText { get; set; }

    /// <summary>
    /// A list of possible answers for the quiz question.
    /// </summary>
    [Parameter]
    public List<string> Answers { get; set; }

    /// <summary>
    /// The callback action provided by the hosting TpcMessageBox (via MessageBoxService)
    /// to communicate the quiz result back and close the dialog.
    /// This is crucial for the asynchronous flow.
    /// </summary>
    [Parameter]
    public Action<QuizResult> OnQuizCompleted { get; set; }

    /// <summary>
    /// Optional: An identifier for the specific question.
    /// Useful if managing a series of quiz questions.
    /// </summary>
    [Parameter]
    public string QuestionId { get; set; }

    private int? _selectedAnswerIndex;

    /// <summary>
    /// Called when a radio button answer is selected.
    /// Updates the internal state for the selected answer.
    /// </summary>
    /// <param name="index">The zero-based index of the selected answer.</param>
    private void OnAnswerSelected(int index)
    {
        _selectedAnswerIndex = index;
        // No need to call StateHasChanged here, Blazor handles it for @bind or @onchange
        // Also, the OK button's disabled state will automatically re-evaluate
    }

    /// <summary>
    /// Handles the click event for the "OK" button.
    /// This confirms the selected answer and closes the quiz dialog.
    /// </summary>
    private void HandleOkClick()
    {
        // Only proceed if an answer is selected
        if (_selectedAnswerIndex.HasValue)
        {
            OnQuizCompleted?.Invoke(new QuizResult(QuizActionType.Ok, _selectedAnswerIndex.Value, QuestionId));
        }
    }

    /// <summary>
    /// Handles the click event for the "Überspringen" button.
    /// Closes the quiz dialog with a 'Skip' action.
    /// </summary>
    private void HandleSkipClick()
    {
        OnQuizCompleted?.Invoke(new QuizResult(QuizActionType.Skip, questionId: QuestionId));
    }

    /// <summary>
    /// Handles the click event for the "Pausieren" button.
    /// Closes the quiz dialog with a 'Pause' action.
    /// </summary>
    private void HandlePauseClick()
    {
        OnQuizCompleted?.Invoke(new QuizResult(QuizActionType.Pause, questionId: QuestionId));
    }

    /// <summary>
    /// Handles the click event for the "Beenden" button.
    /// Closes the quiz dialog with a 'Quit' action.
    /// </summary>
    private void HandleQuitClick()
    {
        OnQuizCompleted?.Invoke(new QuizResult(QuizActionType.Quit, questionId: QuestionId));
    }
}
@* Save this Model under QuizResult.cs *@

namespace [YOUR_PROJECT].Models
{
    /// <summary>
    /// Defines the possible actions a user can take within a quiz dialog.
    /// </summary>
    public enum QuizActionType
    {
        /// <summary>
        /// The user confirmed their answer.
        /// </summary>
        Ok,

        /// <summary>
        /// The user chose to skip the current question.
        /// </summary>
        Skip,

        /// <summary>
        /// The user chose to pause the quiz.
        /// </summary>
        Pause,

        /// <summary>
        /// The user chose to quit the quiz entirely.
        /// </summary>
        Quit,

        /// <summary>
        /// No specific action was taken (e.g., dialog closed via Escape or backdrop).
        /// </summary>
        None
    }

    /// <summary>
    /// Represents the result of a user's interaction with the QuizQuestion component.
    /// </summary>
    public class QuizResult
    {
        /// <summary>
        /// Gets or sets the type of action taken by the user (e.g., OK, Skip, Pause, Quit).
        /// </summary>
        public QuizActionType Action { get; set; }

        /// <summary>
        /// Gets or sets the zero-based index of the selected answer, if an answer was chosen.
        /// This will be null if no answer was selected or if the action was not 'Ok'.
        /// </summary>
        public int? SelectedAnswerIndex { get; set; }

        /// <summary>
        /// Gets or sets an optional ID for the question, useful if managing multiple questions.
        /// </summary>
        public string QuestionId { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="QuizResult"/> class.
        /// </summary>
        /// <param name="action">The action type.</param>
        /// <param name="selectedAnswerIndex">The selected answer index (optional).</param>
        /// <param name="questionId">The question ID (optional).</param>
        public QuizResult(QuizActionType action = QuizActionType.None, int? selectedAnswerIndex = null, string questionId = null)
        {
            Action = action;
            SelectedAnswerIndex = selectedAnswerIndex;
            QuestionId = questionId;
        }
    }
}
           


Usage: P11MessageBoxService Methods

When using the standard ShowOkAsync, ShowYesNoAsync, or ShowYesNoCancelAsync methods, the service internally constructs a MessageBoxRequest using the following properties:
Method Description Parameters Returns
ShowOkAsync(string title, string message, DialogPurpose purpose, string oktext = "Ok") Displays a standard informational message box with a single "OK" button.
  • title (string): Header title.
  • message (string): Main body message.
  • purpose (DialogPurpose): Optional. Influences icon and styling. Default: Information.
  • button (oktext): Optional, button text.
Task<MessageBoxResult> (Ok or None)
ShowYesNoAsync(string title, string message, DialogPurpose purpose, string yestext = "Yes", string notext = "No") Displays a standard message box with "Yes" and "No" buttons for binary decisions.
  • title (string): Header title.
  • message (string): Main body message.
  • purpose (DialogPurpose): Optional. Influences icon and styling. Default: Question. Buttons title optional.
  • buttons (yestext, notext): Optional, buttons text.
Task<MessageBoxResult> (Yes, No, or None)
ShowYesNoCancelAsync(string title, string message, DialogPurpose purpose, string yestext = "Yes", string notext = "No", string canceltext = "Cancel") Displays a standard message box with "Yes", "No", and "Cancel" buttons for actions that can be confirmed, denied, or aborted.
  • title (string): Header title.
  • message (string): Main body message.
  • purpose (DialogPurpose): Optional. Influences icon and styling. Default: Question.
  • buttons (yestext, notext, canceltext): Optional, buttons text.
Task<MessageBoxResult> (Yes, No, Cancel, or None)
ShowAsync<TResult>(RenderFragment<Action<TResult>> content, string title) Displays a highly customizable message box using a provided RenderFragment for its content. The custom content is responsible for invoking the provided Action<TResult> to set the dialog's result and close it.
  • content (RenderFragment<Action<TResult>>): Custom Blazor content for the message box body. Receives a callback to set the result.
  • title (string): Optional. Header title. Default: Information.
Task<TResult> (Completes with the custom result type.)
ShowAsync<TComponent, TResult>(object parameters, string title) where TComponent : IComponent Displays a highly customizable message box by rendering a custom Blazor component (TComponent) inside the dialog. Parameters can be passed to the component via the parameters object. TComponent must expose a [Parameter] Action<TResult> to set the dialog's result.
  • parameters (object): An anonymous object containing parameters to pass to TComponent.
  • title (string): Optional. Header title. Default: Information.
Task<TResult> (Completes with the custom result type.)


Underlying MessageBoxRequest Properties

When using the standard ShowOkAsync, ShowYesNoAsync, or ShowYesNoCancelAsync methods, the service internally constructs a MessageBoxRequest using the following properties:
Property Type Default Description
Title string null Gets or sets the title displayed in the header of the message box.
Purpose DialogPurpose DialogPurpose.Custom Gets or sets the purpose or style of the message box. This typically influences the default icon and visual styling (e.g., info, warning, error, question).
IconClass string null Gets or sets an optional CSS class for a custom icon. If specified, this class overrides the default icon determined by the Purpose.
Message string null Gets or sets the main message text to display in the body of a standard message box. This property is used for non-custom message boxes (e.g., OK-boxes).
Buttons List<(string Text, MessageBoxResult Result)> null (Internal) Defines the text and associated result for the buttons displayed in the message box. Set by the standard Show...Async methods.
CustomContent Func<Action<object>, RenderFragment> null (Internal) Used for custom message boxes to render dynamic Blazor content. Set by the ShowAsync methods.
SetResultCallback Action<object> null (Internal) A callback action invoked by the TpcMessageBox component to deliver the user's selected result back to the awaiting service method.
An unhandled error has occurred. Reload 🗙