Navigation Events
Overview
The EFT Remittance Dashboard implements an event-driven architecture that facilitates communication between components without tight coupling. While traditional navigation events are minimal due to the single-view design, the system extensively uses business events to trigger UI updates, coordinate workflows, and maintain responsive user interactions.
Key Concepts
- Command-Based Events: RelayCommand and RelayCommandAsync for user interactions
- Property Change Notifications: INotifyPropertyChanged for data binding
- Async Event Handling: Task-based asynchronous patterns for long-running operations
- Event Aggregation: Loosely coupled communication between view models
Implementation Details
Event Infrastructure
The event system is built on the WPF commanding infrastructure and property change notifications:
// MepApps.Dash.Ap.Rpt.EftRemittance/ViewModels/BaseViewModel.cs
public class BaseViewModel : IBaseViewModel, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
Command Events
The dashboard uses RelayCommand patterns for handling user interactions:
// MepApps.Dash.Ap.Rpt.EftRemittance/ViewModels/EftRemit_RunReportsViewModel.cs
public class EftRemit_RunReportsViewModel : BaseViewModel
{
// Command declarations
public RelayCommandAsync PreviewRemittancesCmd { get; }
public RelayCommandAsync CheckDataCmd { get; }
public RelayCommandAsync CheckEmailsCmd { get; }
public RelayCommandAsync SendSelectedRemittancesCmd { get; }
public RelayCommandAsync ReloadPaymentDetailsCmd { get; }
// Command initialization in constructor
public EftRemit_RunReportsViewModel()
{
PreviewRemittancesCmd = new RelayCommandAsync(
async () => await PreviewRemittances(),
() => PreviewRemittancesEnabled);
CheckDataCmd = new RelayCommandAsync(
async () => await CheckData(),
() => CheckDataEnabled);
}
}
Selection Events
The dashboard implements sophisticated selection event handling:
// Selection command with parameter
public RelayCommandAsync<IEnumerable<PaymentDetail>> SelectCmd { get; }
public RelayCommandAsync<IEnumerable<PaymentDetail>> DeselectCmd { get; }
// Implementation
SelectCmd = new RelayCommandAsync<IEnumerable<PaymentDetail>>(
async (items) => await SelectItems(items));
private async Task SelectItems(IEnumerable<PaymentDetail> items)
{
foreach (var item in items)
{
item.Selected = true;
}
// Raise property changed for dependent properties
OnPropertyChanged(nameof(SendRemittancesEnabled));
OnPropertyChanged(nameof(SelectedCount));
await Task.CompletedTask;
}
Examples
Example 1: Payment Selection Changed Event
private SelectionItem _selectedPayment;
public SelectionItem SelectedPayment
{
get => _selectedPayment;
set
{
if (SetField(ref _selectedPayment, value))
{
// Trigger cascade of events
Task.Run(async () => await HandlePaymentSelectionChanged());
}
}
}
private async Task HandlePaymentSelectionChanged()
{
// Clear previous details
PaymentDetails.Clear();
// Load new payment details
if (SelectedPayment != null)
{
var details = await _eftRemittanceService.QueryPaymentNumber(
SelectedPayment.Value,
CheckFilter);
// Update UI on dispatcher thread
await _dispatcher.InvokeAsync(() =>
{
foreach (var detail in details)
{
PaymentDetails.Add(detail);
}
});
// Notify dependent properties
OnPropertyChanged(nameof(CheckDataEnabled));
OnPropertyChanged(nameof(PreviewRemittancesEnabled));
}
}
Example 2: Progress Notification Events
public class ProgressEventArgs : EventArgs
{
public int Current { get; set; }
public int Total { get; set; }
public string Message { get; set; }
}
// In service
public event EventHandler<ProgressEventArgs> ProgressChanged;
private async Task ProcessRemittances()
{
var items = PaymentDetails.Where(x => x.Selected).ToList();
for (int i = 0; i < items.Count; i++)
{
// Notify progress
ProgressChanged?.Invoke(this, new ProgressEventArgs
{
Current = i + 1,
Total = items.Count,
Message = $"Processing {items[i].Supplier}..."
});
await ProcessSingleRemittance(items[i]);
}
}
Example 3: Validation Event Chain
private bool _networkShareIsValid;
public bool NetworkShareIsValid
{
get => _networkShareIsValid;
private set
{
if (SetField(ref _networkShareIsValid, value))
{
// Trigger validation chain
OnPropertyChanged(nameof(SendRemittancesEnabled));
OnPropertyChanged(nameof(PreviewRemittancesEnabled));
// Raise validation event
ValidationStateChanged?.Invoke(this,
new ValidationEventArgs { IsValid = value });
}
}
}
Event Patterns
Async Command Pattern
// Pattern for long-running operations
public RelayCommandAsync SendRemittancesCmd { get; }
private async Task SendRemittances()
{
try
{
IsBusy = true; // Triggers UI state change
var selectedItems = PaymentDetails.Where(x => x.Selected);
foreach (var item in selectedItems)
{
await _eftRemittanceService.QueueMailInServiceAsync(
item.Supplier,
item.PaymentNumber,
item.RemitEmail,
BlindCopyList,
$"Remittance Advice - {item.PaymentNumber}");
// Update UI after each item
item.Status = "Sent";
OnPropertyChanged(nameof(PaymentDetails));
}
}
finally
{
IsBusy = false; // Reset UI state
}
}
Property Dependency Chain
// Properties that depend on other properties
public bool SendRemittancesEnabled =>
NetworkShareIsValid &&
PaymentDetails?.Any(x => x.Selected) == true &&
!IsBusy;
public bool CheckDataEnabled =>
SelectedPayment != null &&
!IsBusy;
// When base properties change, notify dependents
private void NotifyDependentProperties()
{
OnPropertyChanged(nameof(SendRemittancesEnabled));
OnPropertyChanged(nameof(CheckDataEnabled));
OnPropertyChanged(nameof(PreviewRemittancesEnabled));
}
Event Bubbling and Handling
Container Event Integration
// MainView.xaml.cs
public partial class MainView : UserControl
{
private readonly IContainerEvents? _events;
public MainView(IContainerEvents? events)
{
_events = events;
if (_events != null)
{
// Subscribe to container events
_events.OnRefresh += HandleRefresh;
_events.OnClose += HandleClose;
}
}
private void HandleRefresh(object sender, EventArgs e)
{
// Reload data when container requests refresh
var viewModel = DataContext as IEftRemit_RunReportsViewModel;
viewModel?.ReloadPaymentDetailsCmd.Execute(null);
}
}
Business Logic Event Integration
Events coordinate between UI and business logic:
// Service raises events
public class EftRemittanceService
{
public event EventHandler<EmailSentEventArgs> EmailSent;
public async Task<int?> SendMail(string mailTo, string subject,
string fileAttachmentPath,
string blindCcRecipients)
{
var result = await MailService.SendAsync(...);
// Raise event after successful send
EmailSent?.Invoke(this, new EmailSentEventArgs
{
Recipient = mailTo,
Subject = subject,
Timestamp = DateTime.Now,
Success = result.HasValue
});
return result;
}
}
// ViewModel subscribes to service events
public EftRemit_RunReportsViewModel(IEftRemittanceService service)
{
service.EmailSent += OnEmailSent;
}
private void OnEmailSent(object sender, EmailSentEventArgs e)
{
// Update UI based on service event
var payment = PaymentDetails.FirstOrDefault(
p => p.RemitEmail == e.Recipient);
if (payment != null)
{
payment.EmailStatus = e.Success ? "Sent" : "Failed";
OnPropertyChanged(nameof(PaymentDetails));
}
}
Best Practices
- Use Weak Event Patterns: Prevent memory leaks with weak references
- Async Event Handlers: Use async/await for long-running operations
- Event Aggregation: Consider event aggregator for complex scenarios
- Unsubscribe Events: Always unsubscribe in Dispose or NavigatedFrom
- Thread Safety: Use Dispatcher for UI updates from background threads
Common Pitfalls
- Memory leaks from not unsubscribing events
- Deadlocks from synchronous event handlers
- UI freezing from long-running synchronous operations
- Race conditions in async event handlers
- Circular event chains causing stack overflow
Related Documentation
Summary
The EFT Remittance Dashboard's event system provides a robust foundation for responsive, maintainable user interfaces. By combining WPF's commanding infrastructure with async patterns and property change notifications, the system enables complex workflows while maintaining clean separation of concerns. The event-driven architecture ensures that UI remains responsive during long-running operations and that components remain loosely coupled for maximum testability and maintainability.