Desktop Automation Patterns
This guide provides comprehensive examples for automating desktop applications across different UI frameworks including WinForms, WPF, Win32, and native Windows applications.
🖥️ Technology Coverage
| Framework | Description | Common Use Cases |
|---|---|---|
| WinForms | Traditional .NET Windows Forms | Legacy applications, Simple desktop apps |
| WPF | Windows Presentation Foundation | Modern .NET desktop applications |
| Win32 | Native Windows API applications | System utilities, Legacy software |
| Office Applications | Microsoft Office automation | Document processing, Reporting |
| System Dialogs | Windows system dialogs | File operations, Security prompts |
WinForms Patterns
Basic WinForms Automation
using Ranorex;
using Ranorex.Core;
using System;
using System.Linq;
public class WinFormsAutomation
{
public void AutomateCalculator()
{
Host.Initialize();
try
{
// Launch Windows Calculator
System.Diagnostics.Process.Start("calc.exe");
// Wait for calculator window
var calculatorWindow = "/form[@processname='Calculator']";
Host.Local.FindSingle<Form>(calculatorWindow, 10000);
// Perform calculation: 123 + 456 = 579
PerformCalculation("123", "+", "456");
// Verify result
var resultDisplay = Host.Local.FindSingle<Text>($"{calculatorWindow}//text[@automationid='CalculatorResults']");
var result = resultDisplay.Text.Replace("Display is ", "").Trim();
if (result == "579")
{
Report.Success("Calculator", "Calculation result is correct: 579");
}
else
{
Report.Failure("Calculator", $"Expected 579, but got: {result}");
}
// Close calculator
var closeButton = Host.Local.FindSingle<Button>($"{calculatorWindow}//button[@name='Close']");
closeButton.Click();
}
catch (Exception ex)
{
Report.Failure("WinForms Automation", $"Failed: {ex.Message}");
ScreenshotHelper.CaptureOnFailure("WinFormsCalculator", ex);
throw;
}
finally
{
Host.Shutdown();
}
}
private void PerformCalculation(string num1, string operation, string num2)
{
var calculatorWindow = "/form[@processname='Calculator']";
// Enter first number
foreach (char digit in num1)
{
var buttonPath = $"{calculatorWindow}//button[@name='{digit}']";
var button = Host.Local.FindSingle<Button>(buttonPath);
button.Click();
Delay.Milliseconds(100);
}
// Click operation
var operationButton = Host.Local.FindSingle<Button>($"{calculatorWindow}//button[@name='{operation}']");
operationButton.Click();
// Enter second number
foreach (char digit in num2)
{
var buttonPath = $"{calculatorWindow}//button[@name='{digit}']";
var button = Host.Local.FindSingle<Button>(buttonPath);
button.Click();
Delay.Milliseconds(100);
}
// Click equals
var equalsButton = Host.Local.FindSingle<Button>($"{calculatorWindow}//button[@name='Equals']");
equalsButton.Click();
}
}
Advanced WinForms Data Grid Automation
using Ranorex;
using System;
using System.Collections.Generic;
using System.Linq;
public class DataGridAutomation
{
/// <summary>
/// Extracts all data from a WinForms DataGridView
/// </summary>
public List<Dictionary<string, string>> ExtractDataGridData(string gridPath)
{
var results = new List<Dictionary<string, string>>();
try
{
var dataGrid = Host.Local.FindSingle<Table>(gridPath);
// Get column headers
var headers = dataGrid.Find<Cell>("./row[1]/cell")
.Select(cell => cell.Text?.Trim())
.ToList();
// Get data rows (skip header row)
var dataRows = dataGrid.Find<Row>("./row").Skip(1);
foreach (var row in dataRows)
{
var cells = row.Find<Cell>("./cell");
var rowData = new Dictionary<string, string>();
for (int i = 0; i < Math.Min(headers.Count, cells.Count); i++)
{
var headerName = headers[i] ?? $"Column{i + 1}";
var cellValue = cells[i].Text?.Trim() ?? "";
rowData[headerName] = cellValue;
}
results.Add(rowData);
}
Report.Info("DataGrid", $"Extracted {results.Count} rows with {headers.Count} columns");
return results;
}
catch (Exception ex)
{
Report.Error("DataGrid", $"Failed to extract data: {ex.Message}");
throw;
}
}
/// <summary>
/// Searches and filters data in a WinForms DataGridView
/// </summary>
public void SearchAndFilterGrid(string gridPath, string searchColumn, string searchValue)
{
try
{
var dataGrid = Host.Local.FindSingle<Table>(gridPath);
// Right-click on column header to access filter
var headers = dataGrid.Find<Cell>("./row[1]/cell");
var targetHeader = headers.FirstOrDefault(h => h.Text?.Contains(searchColumn) == true);
if (targetHeader != null)
{
targetHeader.Click(Location.CenterRight);
// Look for filter option in context menu
var filterOption = Host.Local.FindSingle<MenuItem>("//menuitem[contains(@text, 'Filter')]", 2000);
filterOption.Click();
// Enter search value in filter dialog
var filterDialog = Host.Local.FindSingle<Form>("//form[contains(@title, 'Filter')]", 3000);
var searchBox = filterDialog.FindSingle<Text>("//text[@name='SearchBox' or @name='FilterValue']");
searchBox.Click();
searchBox.PressKeys("{Ctrl}a");
searchBox.PressKeys(searchValue);
// Apply filter
var applyButton = filterDialog.FindSingle<Button>("//button[@text='Apply' or @text='OK']");
applyButton.Click();
Report.Success("DataGrid Filter", $"Applied filter: {searchColumn} = {searchValue}");
}
else
{
throw new ElementNotFoundException($"Column '{searchColumn}' not found");
}
}
catch (Exception ex)
{
Report.Failure("DataGrid Filter", $"Failed to apply filter: {ex.Message}");
throw;
}
}
}
WPF Patterns
WPF Application Automation
using Ranorex;
using System;
using System.Collections.Generic;
public class WpfAutomation
{
/// <summary>
/// Automates WPF TreeView navigation
/// </summary>
public void NavigateWpfTreeView(string treeViewPath, string[] nodePath)
{
try
{
var treeView = Host.Local.FindSingle<Tree>(treeViewPath);
string currentPath = treeViewPath;
foreach (string nodeName in nodePath)
{
// Find the tree item by name
var treeItem = Host.Local.FindSingle<TreeItem>($"{currentPath}//treeitem[@text='{nodeName}']");
// Expand if it has children
if (treeItem.Expanded == false)
{
var expandButton = treeItem.FindSingle<Button>("./button[@name='Expander']", 1000);
if (expandButton.Exists)
{
expandButton.Click();
Delay.Milliseconds(500); // Wait for expansion
}
}
// Update current path for next iteration
currentPath = treeItem.GetPath().ToString();
Report.Info("TreeView", $"Navigated to node: {nodeName}");
}
// Final selection
var finalNode = Host.Local.FindSingle<TreeItem>(currentPath);
finalNode.Click();
Report.Success("TreeView Navigation", "Successfully navigated to target node");
}
catch (Exception ex)
{
Report.Failure("TreeView Navigation", $"Failed: {ex.Message}");
throw;
}
}
/// <summary>
/// Handles WPF ComboBox with complex data
/// </summary>
public void SelectFromWpfComboBox(string comboBoxPath, string displayText)
{
try
{
var comboBox = Host.Local.FindSingle<ComboBox>(comboBoxPath);
// Open dropdown
comboBox.Click();
Delay.Milliseconds(300);
// Try to find item by display text
var dropdownItems = comboBox.Find<ListItem>("./listitem");
var targetItem = dropdownItems.FirstOrDefault(item =>
item.Text?.Contains(displayText) == true ||
item.GetAttributeValue("DisplayText")?.Contains(displayText) == true);
if (targetItem != null)
{
targetItem.Click();
Report.Success("ComboBox", $"Selected item: {displayText}");
}
else
{
// Try typing to search
comboBox.PressKeys(displayText);
comboBox.PressKeys("{Return}");
Report.Info("ComboBox", $"Typed selection: {displayText}");
}
}
catch (Exception ex)
{
Report.Failure("ComboBox Selection", $"Failed to select '{displayText}': {ex.Message}");
throw;
}
}
/// <summary>
/// Automates WPF TabControl navigation
/// </summary>
public void NavigateWpfTabs(string tabControlPath, string tabName)
{
try
{
var tabControl = Host.Local.FindSingle<TabControl>(tabControlPath);
// Find tab by name or text
var tabItem = tabControl.FindSingle<TabItem>($"./tabitem[@text='{tabName}' or @name='{tabName}']");
if (!tabItem.Selected)
{
tabItem.Click();
Delay.Milliseconds(500); // Wait for tab content to load
}
// Verify tab is active
if (tabItem.Selected)
{
Report.Success("Tab Navigation", $"Successfully switched to tab: {tabName}");
}
else
{
throw new Exception($"Failed to activate tab: {tabName}");
}
}
catch (Exception ex)
{
Report.Failure("Tab Navigation", $"Failed: {ex.Message}");
throw;
}
}
}
Win32 Patterns
Win32 Application Automation
using Ranorex;
using System;
using System.Runtime.InteropServices;
public class Win32Automation
{
// Win32 API declarations
[DllImport("user32.dll")]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
const int SW_RESTORE = 9;
/// <summary>
/// Automates legacy Win32 applications using window handles
/// </summary>
public void AutomateLegacyApplication(string windowTitle)
{
try
{
// Find window by title
IntPtr windowHandle = FindWindow(null, windowTitle);
if (windowHandle == IntPtr.Zero)
{
throw new Exception($"Window '{windowTitle}' not found");
}
// Bring window to foreground
ShowWindow(windowHandle, SW_RESTORE);
SetForegroundWindow(windowHandle);
// Convert to Ranorex element
var windowElement = Host.Local.FindSingle<Form>($"/form[@title='{windowTitle}']");
// Automate using Ranorex from here
AutomateWindowContent(windowElement);
Report.Success("Win32 Automation", $"Successfully automated window: {windowTitle}");
}
catch (Exception ex)
{
Report.Failure("Win32 Automation", $"Failed: {ex.Message}");
throw;
}
}
private void AutomateWindowContent(Form window)
{
// Generic window content automation
var buttons = window.Find<Button>("//button");
foreach (var button in buttons)
{
if (button.Text.Contains("OK") || button.Text.Contains("Apply"))
{
Report.Info("Win32", $"Found actionable button: {button.Text}");
}
}
var textFields = window.Find<Text>("//text[@enabled='True']");
foreach (var field in textFields)
{
if (string.IsNullOrEmpty(field.Text))
{
Report.Info("Win32", "Found empty text field for potential input");
}
}
}
/// <summary>
/// Handles Win32 message boxes and dialogs
/// </summary>
public bool HandleMessageBox(string expectedTitle, string buttonToClick = "OK")
{
try
{
var messageBox = Host.Local.FindSingle<Form>($"/form[@title='{expectedTitle}']", 5000);
if (messageBox.Exists)
{
var messageText = messageBox.FindSingle<Text>("//text", 1000);
if (messageText.Exists)
{
Report.Info("Message Box", $"Message: {messageText.Text}");
}
var button = messageBox.FindSingle<Button>($"//button[@text='{buttonToClick}']");
button.Click();
Report.Success("Message Box", $"Handled message box: {expectedTitle}");
return true;
}
return false;
}
catch (Exception ex)
{
Report.Warn("Message Box", $"Failed to handle message box: {ex.Message}");
return false;
}
}
}
Office Automation
Microsoft Office Automation
using Ranorex;
using System;
using System.IO;
public class OfficeAutomation
{
/// <summary>
/// Automates Microsoft Excel operations
/// </summary>
public void AutomateExcel(string filePath)
{
try
{
// Launch Excel with specific file
System.Diagnostics.Process.Start("excel.exe", $"\"{filePath}\"");
// Wait for Excel window
var excelWindow = "/form[@processname='EXCEL']";
Host.Local.FindSingle<Form>(excelWindow, 15000);
// Navigate to specific cell
var cellA1 = Host.Local.FindSingle<Cell>($"{excelWindow}//cell[@name='A1']");
cellA1.Click();
// Enter data
cellA1.PressKeys("Test Data");
cellA1.PressKeys("{Return}");
// Format cell (make it bold)
cellA1.Click();
Host.Local.FindSingle<Form>(excelWindow).PressKeys("{Ctrl}b");
// Save file
Host.Local.FindSingle<Form>(excelWindow).PressKeys("{Ctrl}s");
// Close Excel
Host.Local.FindSingle<Form>(excelWindow).PressKeys("{Alt}{F4}");
Report.Success("Excel Automation", "Successfully automated Excel operations");
}
catch (Exception ex)
{
Report.Failure("Excel Automation", $"Failed: {ex.Message}");
throw;
}
}
/// <summary>
/// Automates Microsoft Word document creation
/// </summary>
public void AutomateWord(string documentTitle)
{
try
{
// Launch Word
System.Diagnostics.Process.Start("winword.exe");
// Wait for Word window
var wordWindow = "/form[@processname='WINWORD']";
Host.Local.FindSingle<Form>(wordWindow, 15000);
// Click in document area
var documentArea = Host.Local.FindSingle<Document>($"{wordWindow}//document");
documentArea.Click();
// Type document content
documentArea.PressKeys($"Title: {documentTitle}");
documentArea.PressKeys("{Return}{Return}");
documentArea.PressKeys("This is automated content created by Ranorex.");
documentArea.PressKeys("{Return}");
documentArea.PressKeys($"Generated on: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
// Format title (select first line and make it bold)
documentArea.PressKeys("{Ctrl}{Home}");
documentArea.PressKeys("{Shift}{End}");
documentArea.PressKeys("{Ctrl}b");
// Move to end
documentArea.PressKeys("{Ctrl}{End}");
Report.Success("Word Automation", "Successfully created Word document");
}
catch (Exception ex)
{
Report.Failure("Word Automation", $"Failed: {ex.Message}");
throw;
}
}
}
System Dialogs
Windows System Dialog Automation
using Ranorex;
using System;
using System.IO;
public class SystemDialogAutomation
{
/// <summary>
/// Handles Windows File Open dialog
/// </summary>
public void HandleFileOpenDialog(string filePath)
{
try
{
// Wait for File Open dialog
var openDialog = Host.Local.FindSingle<Form>("/form[@title='Open' or @title='Open File']", 10000);
// Enter file path in filename field
var filenameField = openDialog.FindSingle<Text>("//text[@name='File name:' or @automationid='1001']");
filenameField.Click();
filenameField.PressKeys("{Ctrl}a");
filenameField.PressKeys(filePath);
// Click Open button
var openButton = openDialog.FindSingle<Button>("//button[@text='Open' or @text='&Open']");
openButton.Click();
Report.Success("File Dialog", $"Successfully opened file: {filePath}");
}
catch (Exception ex)
{
Report.Failure("File Dialog", $"Failed to handle file open dialog: {ex.Message}");
throw;
}
}
/// <summary>
/// Handles Windows File Save dialog
/// </summary>
public void HandleFileSaveDialog(string filePath, string fileName)
{
try
{
// Wait for File Save dialog
var saveDialog = Host.Local.FindSingle<Form>("/form[@title='Save As' or @title='Save']", 10000);
// Navigate to directory if needed
var directory = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(directory))
{
var addressBar = saveDialog.FindSingle<Text>("//text[@name='Address' or @automationid='1000']");
addressBar.Click();
addressBar.PressKeys("{Ctrl}a");
addressBar.PressKeys(directory);
addressBar.PressKeys("{Return}");
WaitHelper.WaitForCondition(() => !saveDialog.FindSingle<Text>("//text[contains(@text, 'Loading')]", 1000).Exists,
"directory navigation to complete");
}
// Enter filename
var filenameField = saveDialog.FindSingle<Text>("//text[@name='File name:']");
filenameField.Click();
filenameField.PressKeys("{Ctrl}a");
filenameField.PressKeys(fileName);
// Click Save button
var saveButton = saveDialog.FindSingle<Button>("//button[@text='Save' or @text='&Save']");
saveButton.Click();
// Handle overwrite confirmation if it appears
try
{
var confirmDialog = Host.Local.FindSingle<Form>("/form[contains(@title, 'Confirm')]", 3000);
var yesButton = confirmDialog.FindSingle<Button>("//button[@text='Yes' or @text='&Yes']");
yesButton.Click();
}
catch (RanorexException)
{
// No confirmation dialog appeared
}
Report.Success("File Dialog", $"Successfully saved file: {fileName}");
}
catch (Exception ex)
{
Report.Failure("File Dialog", $"Failed to handle file save dialog: {ex.Message}");
throw;
}
}
/// <summary>
/// Handles Windows User Account Control (UAC) dialogs
/// </summary>
public bool HandleUacDialog(TimeSpan timeout)
{
try
{
var endTime = DateTime.Now.Add(timeout);
while (DateTime.Now < endTime)
{
// Look for UAC dialog
if (Host.Local.TryFindSingle("/form[@title='User Account Control']", out Form uacDialog))
{
// Click Yes button
var yesButton = uacDialog.FindSingle<Button>("//button[@text='Yes' or @text='&Yes']");
yesButton.Click();
Report.Success("UAC Dialog", "Successfully handled UAC prompt");
return true;
}
System.Threading.Thread.Sleep(500);
}
return false;
}
catch (Exception ex)
{
Report.Warn("UAC Dialog", $"Failed to handle UAC dialog: {ex.Message}");
return false;
}
}
}
Best Practices for Desktop Automation
1. Application Lifecycle Management
- Always initialize and shutdown Ranorex properly
- Handle application startup timeouts gracefully
- Implement proper cleanup in finally blocks
2. Element Identification Strategies
- Use multiple identification strategies (name, automationid, text)
- Implement retry logic for element finding
- Use relative paths when possible for maintainability
3. Timing and Synchronization
- Use explicit waits instead of fixed delays
- Wait for application state changes
- Handle loading indicators and progress bars
4. Error Handling
- Capture screenshots on failures
- Implement graceful error recovery
- Log detailed error information
5. Cross-Framework Compatibility
- Test automation across different OS versions
- Handle framework-specific behaviors
- Use accessibility properties when available
Next Steps
For more advanced desktop automation scenarios:
- Advanced Integration - Database and API integration
- Code Cookbook - Reusable utility methods
- Web Patterns - Browser automation techniques
This desktop automation framework provides the foundation for robust, maintainable automation of Windows desktop applications across all major UI frameworks.