Skip to main content

Dialog Navigation

Overview

The dialog navigation system in MepApps.Dash.Inv.Batch.MiniMrpOrderCreation implements modal dialog patterns for specialized user interactions that require focused attention without leaving the main workflow context. The system provides supplier selection, allocation views, and settings management through a consistent dialog framework.

Key Concepts

  • Modal Dialog Pattern: Focused interaction windows that block main workflow
  • Data Exchange: Bidirectional data flow between parent views and dialogs
  • Dialog Result Handling: Structured approach to processing dialog outcomes
  • Context Preservation: Main view state maintained during dialog interactions

Implementation Details

Dialog Infrastructure

The dialog system is organized under the Views/Dialog and ViewModels/Dialog directories:

Views/Dialog/
├── SupplierSelectionView.xaml
├── SalesOrderAllocationsView.xaml
├── WipAllocationsView.xaml
└── SettingsDialogView.xaml

ViewModels/Dialog/
├── SupplierSelectionViewModel.cs
├── SalesOrderAllocationsViewModel.cs
├── WipAllocationsViewModel.cs
└── SettingsDialogViewModel.cs

Dialog Opening Patterns

1. Supplier Selection Dialog

The supplier selection dialog demonstrates the standard pattern for opening modal dialogs:

// SupplierSelectionViewModel.cs (lines 33-45)
public async void Initialize()
{
try
{
SearchString = string.Empty;
await SearchCmd.ExecuteAsync().ConfigureAwait(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize SupplierSelectionViewModel");
throw;
}
}

public async void Initialize(string searchString)
{
try
{
SearchString = searchString;
await SearchCmd.ExecuteAsync().ConfigureAwait(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in SupplierSelectionViewModel.Initialize");
throw;
}
}

2. Sales Order Allocations Dialog

Shows items allocated to sales orders:

// SalesOrderAllocationsViewModel.cs (lines 32-47)
public async void Initialize(string searchString, WarehouseOrderingListItem warehouseOrderingListItem)
{
try
{
SearchString = searchString;
WarehouseOrderingListItem = warehouseOrderingListItem;
data = GetSalesOrdersJobMaterialsToAttach(warehouseOrderingListItem);

await SearchCmd.ExecuteAsync().ConfigureAwait(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize SalesOrderAllocationsViewModel");
throw;
}
}

3. WIP Allocations Dialog

Displays work-in-progress allocations:

// WipAllocationsViewModel.cs (lines 32-47)
public async void Initialize(string searchString, WarehouseOrderingListItem warehouseOrderingListItem)
{
try
{
SearchString = searchString;
WarehouseOrderingListItem = warehouseOrderingListItem;
data = GetSalesOrdersJobMaterialsToAttach(warehouseOrderingListItem);

await SearchCmd.ExecuteAsync().ConfigureAwait(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize WipAllocationsViewModel");
throw;
}
}

Data Passing to Dialogs

The system implements multiple strategies for passing data to dialogs:

1. Constructor Parameters

// Dialog ViewModels receive dependencies through constructor
public SupplierSelectionViewModel(
ILogger<SupplierSelectionViewModel> logger,
PluginSysproDataContext pluginSysproDataContext,
IPagedDataViewModel<SupplierSelectionListItem> pagedDataViewModel)
{
_logger = logger;
_pluginSysproDataContext = pluginSysproDataContext;
_pagedDataViewModel = pagedDataViewModel;
}

2. Initialization Methods with Context

// Pass context data through initialization
public void Initialize(string searchString, WarehouseOrderingListItem contextItem)
{
SearchString = searchString;
WarehouseOrderingListItem = contextItem;
LoadContextualData();
}

3. Property-Based Data Transfer

// Direct property setting for simple data
dialogViewModel.SearchFilter = parentViewModel.CurrentSearchFilter;
dialogViewModel.SelectedWarehouse = parentViewModel.SelectedWarehouse;

Dialog Result Handling

The system implements structured result handling patterns:

1. Result Object Pattern

public class DialogResult<T>
{
public bool IsSuccess { get; set; }
public T Data { get; set; }
public string Message { get; set; }
public DialogAction Action { get; set; }
}

public enum DialogAction
{
OK,
Cancel,
Apply,
Close
}

2. Event-Based Result Communication

// Supplier selection result event
public event EventHandler<SupplierSelectedEventArgs> SupplierSelected;

private void OnSupplierSelected(SupplierListItem supplier)
{
SupplierSelected?.Invoke(this, new SupplierSelectedEventArgs(supplier));
CloseDialog();
}

3. Command-Based Result Processing

// Return command pattern for dialog results
public RelayCommand<SupplierListItem> SelectSupplierCommand { get; private set; }

SelectSupplierCommand = new RelayCommand<SupplierListItem>(supplier =>
{
SelectedSupplier = supplier;
DialogResult = DialogResult.OK;
Close();
});

Dialog Lifecycle Management

1. Initialization Phase

// SalesOrderAllocationsViewModel.cs (lines 10-25)
public SalesOrderAllocationsViewModel(
ILogger<SalesOrderAllocationsViewModel> logger,
IPagedDataViewModel<AttachedSalesOrderJobMaterial> pagedData,
InvOrderingService invOrderingService)
{
_logger = logger;
try
{
_pagedData = pagedData;
_invOrderingService = invOrderingService;

SearchCmd = new RelayCommandAsync(Search);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to initialize SalesOrderAllocationsViewModel");
throw;
}
}

2. Data Loading Phase

// WipAllocationsViewModel.cs (lines 49-62)
private IEnumerable<AttachedSalesOrderJobMaterial> GetSalesOrdersJobMaterialsToAttach(
WarehouseOrderingListItem item)
{
IEnumerable<AttachedSalesOrderJobMaterial> materials = null;
try
{
materials = _invOrderingService.QueryForSalesOrdersJobMaterials(
item.StockCode, item.Warehouse, "J");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in GetSalesOrdersJobMaterialsToAttach");
throw;
}
return materials;
}

3. Disposal Phase

// SupplierSelectionViewModel.cs (lines 145-153)
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
PagedData?.Dispose();
_initialized = false;
}
}

Dialog Stacking and Chaining

The system supports dialog chaining for complex workflows:

// Open supplier selection, then open allocation view
private async Task ShowSupplierThenAllocations()
{
// Open supplier selection dialog
var supplierDialog = new SupplierSelectionView();
var supplierResult = await supplierDialog.ShowDialogAsync();

if (supplierResult.IsSuccess)
{
// Chain to allocations dialog with selected supplier
var allocationsDialog = new SalesOrderAllocationsView();
allocationsDialog.Initialize(supplierResult.Data);
var allocationResult = await allocationsDialog.ShowDialogAsync();

// Process combined results
ProcessSupplierAndAllocations(supplierResult.Data, allocationResult.Data);
}
}

Settings Dialog Implementation

The settings dialog demonstrates configuration management:

// SettingsDialogViewModel.cs
public class SettingsDialogViewModel : BaseViewModel
{
private readonly IMepSettingsService _mepSettingsService;

public async Task LoadSettingsAsync()
{
// Load current settings
OrderByWarehouse = _mepSettingsService.GetBool("OrderByWarehouse");
OrderByDueDate = _mepSettingsService.GetBool("OrderByDueDate");
AutoSearchOnSupplierSelect = _mepSettingsService.GetBool("AutoSearchOnSupplierSelect");
}

public async Task SaveSettingsAsync()
{
// Save settings based on scope
await _mepSettingsService.SetByCompanyAsync("OrderByWarehouse", OrderByWarehouse.ToString());
await _mepSettingsService.SetByCompanyAsync("OrderByDueDate", OrderByDueDate.ToString());
await _mepSettingsService.SetByOperatorAsync("AutoSearchOnSupplierSelect",
AutoSearchOnSupplierSelect.ToString());
}
}

Examples

Example 1: Opening a Selection Dialog

// Open supplier selection dialog from parent view
private async Task SelectSupplier()
{
var dialog = _serviceProvider.GetService<SupplierSelectionView>();
var viewModel = dialog.DataContext as SupplierSelectionViewModel;

// Initialize with current search context
viewModel.Initialize(_currentSearchString);

// Subscribe to selection event
viewModel.SupplierSelected += OnSupplierSelected;

// Show dialog
var result = await dialog.ShowDialogAsync();

// Cleanup
viewModel.SupplierSelected -= OnSupplierSelected;
viewModel.Dispose();
}

Example 2: Paged Data in Dialogs

// SupplierSelectionViewModel.cs (lines 88-134)
public IQueryable<SupplierSelectionListItem> GetQueryable()
{
try
{
var queryable = from s in _pluginSysproDataContext.ApSupplier
join ad in _pluginSysproDataContext.ApSupplierAddr
on s.Supplier equals ad.Supplier
join b in _pluginSysproDataContext.ApBank
on s.Bank equals b.Bank into bankJoin
from bank in bankJoin.DefaultIfEmpty()
select new SupplierSelectionListItem
{
Supplier = s.Supplier,
SupplierName = s.SupplierName,
Bank = s.Bank,
BankDescription = bank == null ? "" : bank.Description,
CurrentBalance = s.CurrentBalance ?? 0,
// ... address fields
};

if (!string.IsNullOrEmpty(SearchString))
{
var searchstring = SearchString.ToUpper();
queryable = queryable.Where(x =>
x.Supplier.ToUpper().Contains(searchstring) ||
x.SupplierName.ToUpper().Contains(searchstring));
}

return queryable.OrderBy(x => x.Supplier);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get SupplierSelectionListItem queryable");
throw;
}
}

Example 3: Dialog with Validation

// Settings dialog with validation before closing
private bool ValidateSettings()
{
var errors = new List<string>();

if (OrderByWarehouse && OrderByDueDate)
{
errors.Add("Cannot order by both warehouse and due date simultaneously");
}

if (MaxOrderLines < 1 || MaxOrderLines > 1000)
{
errors.Add("Max order lines must be between 1 and 1000");
}

if (errors.Any())
{
ShowValidationErrors(errors);
return false;
}

return true;
}

private async Task SaveAndClose()
{
if (ValidateSettings())
{
await SaveSettingsAsync();
DialogResult = DialogResult.OK;
Close();
}
}

Best Practices

Dialog Design Guidelines

  • Keep dialogs focused on a single task
  • Provide clear action buttons (OK, Cancel, Apply)
  • Show loading indicators for async operations
  • Validate input before allowing dialog closure

Memory Management

  • Always dispose dialog ViewModels
  • Unsubscribe from events when dialog closes
  • Clear large data collections after use
  • Use weak references for long-lived dialogs

Error Handling

  • Show user-friendly error messages in dialogs
  • Log technical details for debugging
  • Provide retry options for recoverable errors
  • Maintain dialog state during error recovery

Accessibility

  • Ensure keyboard navigation works properly
  • Set appropriate tab order
  • Provide keyboard shortcuts for common actions
  • Support screen readers with proper labels

Common Pitfalls

Not Disposing Dialog Resources

// BAD: Dialog ViewModel not disposed
var dialog = new SupplierSelectionView();
await dialog.ShowDialogAsync();
// Resources leaked!

// GOOD: Proper disposal
var dialog = new SupplierSelectionView();
try
{
await dialog.ShowDialogAsync();
}
finally
{
(dialog.DataContext as IDisposable)?.Dispose();
}

Blocking UI Thread in Dialogs

// BAD: Synchronous data loading
public void Initialize()
{
Data = LoadDataFromDatabase(); // Blocks UI
}

// GOOD: Async data loading
public async Task InitializeAsync()
{
IsLoading = true;
Data = await LoadDataFromDatabaseAsync();
IsLoading = false;
}

Tight Coupling Between Dialog and Parent

// BAD: Dialog directly modifies parent
public ParentViewModel ParentVM { get; set; }
private void SaveData()
{
ParentVM.Data = this.SelectedData; // Tight coupling
}

// GOOD: Event-based communication
public event EventHandler<DataSelectedEventArgs> DataSelected;
private void SaveData()
{
DataSelected?.Invoke(this, new DataSelectedEventArgs(SelectedData));
}

Summary

The dialog navigation system in MiniMrpOrderCreation provides a robust framework for modal interactions that complement the main workflow. Through proper lifecycle management, structured result handling, and clear data exchange patterns, dialogs enhance user experience while maintaining application stability. The system's support for validation, paging, and chaining enables complex interactions while keeping the implementation maintainable and testable.