Skip to main content

Dialog Navigation

Overview

The EFT Remittance Dashboard implements a lightweight dialog navigation system that handles modal interactions, user confirmations, and report previews. While the dashboard maintains a single-view architecture, it leverages both WPF's built-in dialog capabilities and SYSPRO's reporting infrastructure for rich modal experiences.

Key Concepts

  • Modal Dialogs: Blocking user interactions for critical decisions
  • Report Preview Windows: SSRS report viewer integration
  • Confirmation Dialogs: User confirmation for destructive actions
  • Progress Dialogs: Feedback during long-running operations
  • Error Dialogs: Structured error presentation

Implementation Details

Dialog Infrastructure

The dialog system is built on the ViewWindows utilities from the MepDash module:

// Using MepApps.Erp.Syspro.Win.Module.Utilities.ViewWindows
using MepApps.Erp.Syspro.Win.Module.Utilities.ViewWindows;

public class DialogService
{
private readonly ISharedShellInterface _sharedShellInterface;

public async Task<bool> ShowConfirmation(string message, string title)
{
var result = MessageBox.Show(
message,
title,
MessageBoxButton.YesNo,
MessageBoxImage.Question);

return result == MessageBoxResult.Yes;
}

public void ShowError(string message, string title = "Error")
{
MessageBox.Show(
message,
title,
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}

Report Preview Dialog

The most sophisticated dialog in the system is the SSRS report preview:

// MepApps.Dash.Ap.Rpt.EftRemittance/ViewModels/EftRemit_RunReportsViewModel.cs
public async Task PreviewReportAsync(string paymentNumber, string supplier)
{
try
{
// Build report parameters
var reportParams = new List<ReportParameter>
{
new ReportParameter("PaymentNumber", paymentNumber),
new ReportParameter("Supplier", supplier),
new ReportParameter("Company", _sharedShellInterface.CurrentSession.SysproCompany)
};

// Configure report viewer
var reportViewer = new ReportViewerWindow
{
ReportPath = "/MepApps/EftRemittance/RemittanceAdvice",
Parameters = reportParams,
Title = $"Remittance Preview - {supplier}",
Width = 1024,
Height = 768
};

// Show modal dialog
reportViewer.ShowDialog();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to preview report. {@PreviewContext}",
new { paymentNumber, supplier });
ShowError($"Failed to preview report: {ex.Message}");
}
}

Confirmation Dialog Pattern

Critical operations use confirmation dialogs:

private async Task<bool> ConfirmSendRemittances()
{
var selectedCount = PaymentDetails.Count(x => x.Selected);

if (selectedCount == 0)
{
ShowInformation("Please select at least one remittance to send.");
return false;
}

var message = selectedCount == 1
? "Send the selected remittance?"
: $"Send {selectedCount} remittances?";

var result = MessageBox.Show(
message,
"Confirm Send",
MessageBoxButton.YesNo,
MessageBoxImage.Question,
MessageBoxResult.No); // Default to No for safety

return result == MessageBoxResult.Yes;
}

// Usage
private async Task SendSelectedRemittances()
{
if (!await ConfirmSendRemittances())
return;

// Proceed with sending...
}

Examples

Example 1: Progress Dialog Implementation

public class ProgressDialog : Window
{
private readonly ProgressBar _progressBar;
private readonly TextBlock _statusText;

public ProgressDialog()
{
Title = "Processing...";
Width = 400;
Height = 150;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
ResizeMode = ResizeMode.NoResize;

var grid = new Grid();
_progressBar = new ProgressBar
{
Minimum = 0,
Maximum = 100,
Height = 20,
Margin = new Thickness(20)
};
_statusText = new TextBlock
{
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(0, 60, 0, 0)
};

grid.Children.Add(_progressBar);
grid.Children.Add(_statusText);
Content = grid;
}

public void UpdateProgress(int current, int total, string message)
{
Dispatcher.Invoke(() =>
{
_progressBar.Value = (double)current / total * 100;
_statusText.Text = message;
});
}
}

// Usage in ViewModel
private async Task ProcessWithProgress()
{
var progressDialog = new ProgressDialog();
progressDialog.Show();

try
{
var items = PaymentDetails.Where(x => x.Selected).ToList();

for (int i = 0; i < items.Count; i++)
{
progressDialog.UpdateProgress(
i + 1,
items.Count,
$"Processing {items[i].Supplier}...");

await ProcessItem(items[i]);
}
}
finally
{
progressDialog.Close();
}
}

Example 2: Email Validation Dialog

public class EmailValidationDialog : Window
{
private readonly DataGrid _emailGrid;
private readonly ObservableCollection<EmailValidationItem> _items;

public bool DialogResult { get; private set; }

public EmailValidationDialog(IEnumerable<PaymentDetail> payments)
{
Title = "Validate Email Addresses";
Width = 600;
Height = 400;

_items = new ObservableCollection<EmailValidationItem>(
payments.Select(p => new EmailValidationItem
{
Supplier = p.Supplier,
SupplierName = p.SupplierName,
Email = p.RemitEmail,
IsValid = ValidateEmail(p.RemitEmail)
}));

InitializeUI();
}

private void InitializeUI()
{
var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

_emailGrid = new DataGrid
{
ItemsSource = _items,
AutoGenerateColumns = false,
CanUserAddRows = false
};

// Add columns
_emailGrid.Columns.Add(new DataGridTextColumn
{
Header = "Supplier",
Binding = new Binding("Supplier"),
IsReadOnly = true
});
_emailGrid.Columns.Add(new DataGridTextColumn
{
Header = "Email",
Binding = new Binding("Email")
});
_emailGrid.Columns.Add(new DataGridCheckBoxColumn
{
Header = "Valid",
Binding = new Binding("IsValid"),
IsReadOnly = true
});

// Add buttons
var buttonPanel = new StackPanel
{
Orientation = Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Right,
Margin = new Thickness(5)
};

var saveButton = new Button { Content = "Save Changes", Margin = new Thickness(5) };
saveButton.Click += (s, e) => { DialogResult = true; Close(); };

var cancelButton = new Button { Content = "Cancel", Margin = new Thickness(5) };
cancelButton.Click += (s, e) => { DialogResult = false; Close(); };

buttonPanel.Children.Add(saveButton);
buttonPanel.Children.Add(cancelButton);

Grid.SetRow(_emailGrid, 0);
Grid.SetRow(buttonPanel, 1);

grid.Children.Add(_emailGrid);
grid.Children.Add(buttonPanel);

Content = grid;
}
}

Example 3: Error Dialog with Details

public class DetailedErrorDialog : Window
{
public DetailedErrorDialog(Exception exception, string context)
{
Title = "Error Details";
Width = 600;
Height = 400;

var grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

// Error message
var messageText = new TextBlock
{
Text = exception.Message,
Margin = new Thickness(10),
TextWrapping = TextWrapping.Wrap,
FontWeight = FontWeights.Bold
};

// Stack trace
var detailsText = new TextBox
{
Text = $"Context: {context}\n\n{exception.StackTrace}",
IsReadOnly = true,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
Margin = new Thickness(10),
FontFamily = new FontFamily("Consolas")
};

// Close button
var closeButton = new Button
{
Content = "Close",
Width = 80,
Margin = new Thickness(10),
HorizontalAlignment = HorizontalAlignment.Right
};
closeButton.Click += (s, e) => Close();

Grid.SetRow(messageText, 0);
Grid.SetRow(detailsText, 1);
Grid.SetRow(closeButton, 2);

grid.Children.Add(messageText);
grid.Children.Add(detailsText);
grid.Children.Add(closeButton);

Content = grid;
}
}

Dialog Data Passing

Input Dialog Pattern

public class InputDialog : Window
{
private TextBox _inputBox;

public string InputValue { get; private set; }

public InputDialog(string prompt, string defaultValue = "")
{
Title = "Input Required";
Width = 400;
Height = 150;
WindowStartupLocation = WindowStartupLocation.CenterOwner;

var stack = new StackPanel { Margin = new Thickness(10) };

stack.Children.Add(new TextBlock { Text = prompt, Margin = new Thickness(0, 0, 0, 10) });

_inputBox = new TextBox { Text = defaultValue };
stack.Children.Add(_inputBox);

var buttonPanel = new StackPanel
{
Orientation = Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Right,
Margin = new Thickness(0, 10, 0, 0)
};

var okButton = new Button { Content = "OK", Width = 75, Margin = new Thickness(5) };
okButton.Click += (s, e) =>
{
InputValue = _inputBox.Text;
DialogResult = true;
Close();
};

var cancelButton = new Button { Content = "Cancel", Width = 75, Margin = new Thickness(5) };
cancelButton.Click += (s, e) =>
{
DialogResult = false;
Close();
};

buttonPanel.Children.Add(okButton);
buttonPanel.Children.Add(cancelButton);
stack.Children.Add(buttonPanel);

Content = stack;
}
}

// Usage
var dialog = new InputDialog("Enter blind copy email addresses (comma-separated):", BlindCopyList);
if (dialog.ShowDialog() == true)
{
BlindCopyList = dialog.InputValue;
}

Dialog Result Handling

public enum RemittanceAction
{
Preview,
Send,
Cancel
}

public class RemittanceActionDialog : Window
{
public RemittanceAction SelectedAction { get; private set; }

private void OnPreviewClick(object sender, RoutedEventArgs e)
{
SelectedAction = RemittanceAction.Preview;
DialogResult = true;
}

private void OnSendClick(object sender, RoutedEventArgs e)
{
SelectedAction = RemittanceAction.Send;
DialogResult = true;
}
}

// Usage
var actionDialog = new RemittanceActionDialog();
if (actionDialog.ShowDialog() == true)
{
switch (actionDialog.SelectedAction)
{
case RemittanceAction.Preview:
await PreviewRemittance();
break;
case RemittanceAction.Send:
await SendRemittance();
break;
}
}

Best Practices

  1. Always Set Owner: Set the Owner property for proper modal behavior
  2. Use DialogResult: Return meaningful results from dialogs
  3. Handle Escape Key: Allow users to cancel with Escape
  4. Validate Input: Validate user input before closing dialogs
  5. Show Progress: Use progress dialogs for long operations
  6. Log Dialog Actions: Log important dialog decisions for audit

Common Pitfalls

  • Not setting dialog owner causing z-order issues
  • Blocking UI thread during dialog operations
  • Not handling dialog cancellation properly
  • Memory leaks from unclosed dialogs
  • Missing validation causing invalid data entry

Summary

The EFT Remittance Dashboard's dialog navigation system provides a clean, consistent approach to modal interactions. By combining WPF's dialog capabilities with custom implementations for specific needs like report previews and email validation, the system ensures a smooth user experience while maintaining data integrity and providing appropriate feedback during long-running operations.