Skip to main content

Navigation Events

Overview

The navigation event system in MepApps.Dash.Inv.Batch.MiniMrpOrderCreation implements a comprehensive event-driven architecture that facilitates loosely-coupled communication between components during navigation operations. This system enables responsive UI updates, state synchronization, and workflow coordination without creating tight dependencies between views and view models.

Key Concepts

  • Event-Driven Navigation: Navigation triggers and responds to domain events
  • Custom EventArgs Models: Strongly-typed event data carriers
  • Event Aggregation: Centralized event distribution across components
  • Asynchronous Event Handling: Non-blocking event processing

Implementation Details

Core Event Infrastructure

The event system is built on custom EventArgs classes located in the Models/EventArgModels directory:

// Models/EventArgModels/NotificationEventArgs.cs
public class NotificationEventArgs : EventArgs
{
public string Title { get; set; }
public string Message { get; set; }
public NotificationType Type { get; set; }
public Exception Exception { get; set; }

public NotificationEventArgs(string title, string message,
NotificationType type = NotificationType.Information)
{
Title = title;
Message = message;
Type = type;
}
}

1. SelectedItemsChangedEventArgs

Triggered when user selections change, affecting navigation availability:

// Models/EventArgModels/SelectedItemsChangedEventArgs.cs
public class SelectedItemsChangedEventArgs<T> : EventArgs
{
public IEnumerable<T> SelectedItems { get; set; }
public int Count { get; set; }
public bool HasSelections => Count > 0;

public SelectedItemsChangedEventArgs(IEnumerable<T> selectedItems)
{
SelectedItems = selectedItems;
Count = selectedItems?.Count() ?? 0;
}
}

Usage in WarehouseOrderingViewModel:

// WarehouseOrderingViewModel.cs (line 96)
public event EventHandler<SelectedItemsChangedEventArgs<WarehouseOrderingListItem>>? SelectedItemsChangedEvent;

private void SelectedItems_ListChanged(object sender, ListChangedEventArgs e)
{
SelectedItemsCount = SelectedItems.Count;
SelectedItemsChangedEvent?.Invoke(this,
new SelectedItemsChangedEventArgs<WarehouseOrderingListItem>(SelectedItems));
}

2. SupplierSelectedEventArgs

Handles supplier selection events that trigger navigation updates:

// Models/EventArgModels/SupplierSelectedEventArgs.cs
public class SupplierSelectedEventArgs : EventArgs
{
public SupplierListItem SelectedSupplier { get; set; }
public bool IsMultiSelection { get; set; }
public IEnumerable<SupplierListItem> SelectedSuppliers { get; set; }

public SupplierSelectedEventArgs(SupplierListItem supplier)
{
SelectedSupplier = supplier;
IsMultiSelection = false;
}
}

3. LaunchSysproProgramEventArgs

Triggers external SYSPRO program launches:

// Models/EventArgModels/LaunchSysproProgramEventArgs.cs
public class LaunchSysproProgramEventArgs : EventArgs
{
public string ProgramName { get; set; }
public string Parameters { get; set; }
public bool WaitForExit { get; set; }

public LaunchSysproProgramEventArgs(string programName, string parameters = null)
{
ProgramName = programName;
Parameters = parameters;
WaitForExit = false;
}
}

Event-Driven Navigation Patterns

1. Property Change Navigation

Navigation triggered by property changes:

// WarehouseOrderingViewModel.cs (lines 74-94)
private async void WarehouseOrderingViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
try
{
switch (e.PropertyName)
{
case "SelectedBuyer":
OrderingEnabled = _selectedBuyer != null;
break;
case "OrderingEnabled":
if (_orderingEnabled)
await SearchCmd.ExecuteAsync().ConfigureAwait(false);
break;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in WarehouseOrderingViewModel_PropertyChanged");
NotificationEvent?.Invoke(this, new NotificationEventArgs(
"Error in WarehouseOrderingViewModel_PropertyChanged", ex.Message));
}
}

2. Command-Based Navigation Events

Navigation initiated through command execution:

// WarehouseOrderingViewModel.cs (lines 366-394)
private async Task CreateordersAsync()
{
try
{
IsLoading = true;
var orderSummaries = _invOrderingService.CreateOrderSummary(
SelectedItems, SelectedBuyer.Buyer,
_mepSettingsService.GetBool("OrderByWarehouse"),
_mepSettingsService.GetBool("OrderByDueDate"));

var results = await _invOrderingService.CreateOrders(
orderSummaries, SelectedBuyer.Buyer).ConfigureAwait(true);

if (!results.IsSuccess)
{
System.Windows.MessageBox.Show(results.StatusMessage, "Error",
MessageBoxButton.OK, MessageBoxImage.Error);
return;
}

_logger.LogInformation("Order creation completed");

// Navigation event triggered after successful operation
var navBackRelayCommand = new RelayCommandAsync(ResetSearch);
_navToOrderCompletion.NavToOrderCompletionView(results, navBackRelayCommand);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error creating orders");
NotificationEvent?.Invoke(this, new NotificationEventArgs(
"Error creating orders", ex.Message));
}
finally
{
IsLoading = false;
}
}

3. Host Notification Events

Main content changes triggered through host notifications:

// MainViewModel.cs (lines 24, 74-90)
_navigationUiHostNotificator.MainContentChanged += (sender, e) => SetMainContent(sender);

public void SetMainContent(object content)
{
Dispatcher.CurrentDispatcher.Invoke(() =>
{
try
{
if (_logger.IsEnabled(LogLevel.Trace))
_logger.LogTrace("CHANGING MAIN CONTENT to {ContentIsNull}, {NameOfContent} {SizeOfContent}",
content == null, content?.GetType().Name, content?.ToString().Length);

MainContent = content;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to set MainContent");
throw;
}
});
}

Event Bubbling and Handling Strategies

1. Direct Event Subscription

// Direct subscription in constructor
public WarehouseOrderingViewModel()
{
SelectedItems.ListChanged += SelectedItems_ListChanged;
PropertyChanged += WarehouseOrderingViewModel_PropertyChanged;
}

2. Weak Event Pattern

For preventing memory leaks:

// Using weak event patterns
WeakEventManager<BindingList<InvOrderingOrderSummaryListItem>, ListChangedEventArgs>
.AddHandler(SelectedItems, "ListChanged", SelectedItems_ListChanged);

3. Event Aggregator Pattern

For cross-component communication:

public class NavigationEventAggregator
{
private readonly Dictionary<Type, List<Delegate>> _eventHandlers = new();

public void Subscribe<TEvent>(Action<TEvent> handler) where TEvent : EventArgs
{
var eventType = typeof(TEvent);
if (!_eventHandlers.ContainsKey(eventType))
_eventHandlers[eventType] = new List<Delegate>();

_eventHandlers[eventType].Add(handler);
}

public void Publish<TEvent>(TEvent eventArgs) where TEvent : EventArgs
{
var eventType = typeof(TEvent);
if (_eventHandlers.ContainsKey(eventType))
{
foreach (var handler in _eventHandlers[eventType])
{
(handler as Action<TEvent>)?.Invoke(eventArgs);
}
}
}
}

Events coordinate between navigation and business operations:

// InvOrderingCompletionViewModel.cs (line 32)
public event EventHandler<LaunchSysproProgramEventArgs>? LaunchSysproProgramEvent;

// Trigger SYSPRO program launch after navigation
private void LaunchPurchaseOrderMaintenance(string orderNumber)
{
LaunchSysproProgramEvent?.Invoke(this,
new LaunchSysproProgramEventArgs("PORMNT", $"OrderNumber={orderNumber}"));
}

Examples

Example 1: Notification Event During Navigation

// WarehouseOrderingViewModel.cs - Error notification
try
{
await NavigateToNextStepAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Navigation failed");
NotificationEvent?.Invoke(this, new NotificationEventArgs(
"Navigation Error",
"Unable to proceed to next step: " + ex.Message,
NotificationType.Error));
}

Example 2: Selection-Based Navigation Enable/Disable

// Enable navigation based on selection events
private void HandleSelectionChanged(object sender,
SelectedItemsChangedEventArgs<WarehouseOrderingListItem> e)
{
CreateOrdersCmd.CanExecute = e.HasSelections && e.Count > 0;
PurchaseOrderCount = CalculatePurchaseOrderCount(e.SelectedItems);

if (e.HasSelections)
{
EnableNavigationToOrderCreation();
}
else
{
DisableNavigationToOrderCreation();
}
}

Example 3: Cascading Navigation Events

// Multiple events triggering navigation flow
private async void HandleSupplierSelected(object sender, SupplierSelectedEventArgs e)
{
// Update supplier
SelectedSupplier = e.SelectedSupplier;

// Trigger warehouse filter update
await UpdateWarehouseFilterAsync(e.SelectedSupplier);

// Enable search
SearchEnabled = true;

// Auto-search if configured
if (_mepSettingsService.GetBool("AutoSearchOnSupplierSelect"))
{
await SearchCmd.ExecuteAsync();

// Navigate to results if found
if (Data?.Any() == true)
{
NotificationEvent?.Invoke(this, new NotificationEventArgs(
"Search Complete",
$"Found {Data.Count()} items for supplier {e.SelectedSupplier.SupplierName}"));
}
}
}

Best Practices

Event Naming Conventions

  • Use descriptive event names ending with "Event"
  • EventArgs classes should end with "EventArgs"
  • Keep event names consistent with their purpose

Memory Management

  • Always unsubscribe from events in Dispose methods
  • Use weak events for long-lived subscribers
  • Avoid capturing unnecessary references in event handlers

Error Handling in Event Handlers

  • Wrap event handler logic in try-catch blocks
  • Log exceptions but don't let them crash the application
  • Provide user feedback for navigation failures

Asynchronous Event Handling

  • Use async void only for event handlers
  • Handle exceptions within async event handlers
  • Consider using Task-based event patterns for better error propagation

Common Pitfalls

Memory Leaks from Event Subscriptions

// BAD: Not unsubscribing from events
public void Initialize()
{
someService.SomeEvent += HandleEvent;
// Never unsubscribed - memory leak!
}

// GOOD: Proper cleanup
public void Dispose()
{
someService.SomeEvent -= HandleEvent;
}

Race Conditions in Event Handlers

// BAD: Not checking for null
NotificationEvent.Invoke(this, args); // NullReferenceException possible

// GOOD: Null-safe invocation
NotificationEvent?.Invoke(this, args);

Blocking UI Thread

// BAD: Synchronous operations in event handlers
private void HandleNavigationEvent(object sender, EventArgs e)
{
Thread.Sleep(5000); // Blocks UI
}

// GOOD: Async operations
private async void HandleNavigationEvent(object sender, EventArgs e)
{
await Task.Delay(5000);
}

Summary

The navigation event system provides a robust foundation for coordinating complex navigation workflows in the MiniMrpOrderCreation dashboard. Through custom EventArgs models, event-driven patterns, and proper event handling strategies, the system maintains loose coupling while ensuring responsive and reliable navigation. The integration with business logic through events enables sophisticated workflow coordination without creating tight dependencies between components.