I propose a different method to let the Applications communicate (in this case, it's a on-way interaction: only one app talks to the other).
Using UI Automation, you can get or set the value of Controls' properties in another application in a semi-transparent way. Also receive or cause events, detect when an application is run or closed an other fun activities. General documentation here:
Windows Accessibility API reference - UI Automation
.Net Framework UI Automation Fundamentals
You have two simple applications with a simple requirement, so this task is quite straightforward:
- One application awaits commands (or it's simply there).
- Another applications sends commands that the first application needs to interpret and act upon.
Since you need to send some strings that change the color of a PictureBox, we can use a TextBox control which will receive the strings and convert the command in a Color or trigger another, predefined, behaviour.
Using UI Automation, the two applicatons can be run independently, then acknowledge the existence of the ohter in different ways. For example, using a WindowPattern.WindowOpenedEvent, we can detect when an application is run and determine if it's interesting in different ways. See these questions for an implementation:
Run event when any Form loads
This other question, to identify an application based on the content of a child control.
Here (to keep it short), I'm simply enumerating the running application which have an interface and choose one using a ComboBox as selector.
private void comboBox1_SelectionChangeCommitted(object sender, EventArgs e)
{
var window = AutomationElement.FromHandle((IntPtr)comboBox1.SelectedValue);
if (window != null) {
GetCommElement(window, ControlType.Edit);
}
}
private void GetCommElement(AutomationElement parent, ControlType controlType)
{
element = parent.FindFirst(TreeScope.Subtree,
new PropertyCondition(AutomationElement.ControlTypeProperty, controlType));
}
If the TextBox of the selected application is found, we get its ValuePattern (a UI Automation pattern that allows to set the value of a control) and set its Text property to a string that corrensponds to the name of a Color:
(Note that only a non-multiline Edit control (a WinForms TextBox) supports the ValuePattern
. A multiline Edit control does not, just the TextRangePattern)
private void btnColor_Click(object sender, EventArgs e)
{
if (element == null) return;
var ctrl = sender as Control;
if (element.TryGetCurrentPattern(ValuePattern.Pattern, out object pattern)) {
(pattern as ValuePattern).SetValue(ctrl.Text);
this.Activate();
}
}
The application that receives the commands, uses its TextBox control's TextChanged
event to receive the strings from the other application and decide what to do:
(Note that the TextBox can be offscreen, but it's Visible
property must be set to true
)
private void textBox1_TextChanged(object sender, EventArgs e)
{
var color = Color.FromName((sender as Control).Text);
pictureBox1.BackColor = (color.IsKnownColor) ? color: Color.White;
}
Sample functionality:

Complete source code of the two applications:
UI Automation require a reference to these assemblies: UIAutomationClient
and UIAutomationTypes
UIAClientApp
:
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Automation;
public partial class UIAClientApp : Form
{
AutomationElement element = null;
private void comboBox1_DropDown(object sender, EventArgs e)
{
var dict = new Dictionary<IntPtr, string>();
foreach(var proc in Process.GetProcesses().Where(p => p.Id > 4 &&
p.MainWindowHandle != this.Handle &&
!string.IsNullOrEmpty(p.MainWindowTitle)).ToList())
{
dict.Add(proc.MainWindowHandle, proc.MainWindowTitle);
}
comboBox1.DisplayMember = "Value";
comboBox1.ValueMember = "Key";
comboBox1.DataSource = dict.ToList();
}
private void comboBox1_SelectionChangeCommitted(object sender, EventArgs e)
{
lblCurrentApp.Text = comboBox1.SelectedItem.ToString();
var window = AutomationElement.FromHandle((IntPtr)comboBox1.SelectedValue);
if (window != null) {
GetCommElement(window, ControlType.Edit);
}
}
private void GetCommElement(AutomationElement parent, ControlType controlType)
{
element = parent.FindFirst(TreeScope.Subtree,
new PropertyCondition(AutomationElement.ControlTypeProperty, controlType));
}
private void btnColor_Click(object sender, EventArgs e)
{
if (element is null) return;
var ctrl = sender as Control;
if (element.TryGetCurrentPattern(ValuePattern.Pattern, out object pattern)) {
(pattern as ValuePattern).SetValue(ctrl.Text);
this.Activate();
}
}
}
UIATestApp
:
using System.Drawing;
using System.Windows.Forms;
public partial class UIATestApp : Form
{
public UIATestApp() => InitializeComponent();
private void textBox1_TextChanged(object sender, EventArgs e)
{
var color = Color.FromName((sender as Control).Text);
pictureBox1.BackColor = (color.IsKnownColor) ? color: Color.White;
}
}