Если вы не пользуетесь каким-либо Фреймворком, то в качестве посредника вполне может выступить само окно программы или активный в данный момент UserControl. Привожу пример где в качестве посредника выступает кодбихайнд окна программы.
Типа калькулятор:) PageCalc.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBox x:Name="textBoxInput" Text="{Binding InputCalc, Mode=TwoWay}" Width="100" Margin="10" />
<Button Content="1" Margin="10"
Command="{Binding NumberInputCommand}"
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"/>
<Button Content="2" Margin="10"
Command="{Binding NumberInputCommand}"
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"/>
<Button Content="3" Margin="10"
Command="{Binding NumberInputCommand}"
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"/>
<Button Content="OK" Margin="10" Command="{Binding CalcOkCommand, Mode=OneTime}"/>
</StackPanel>
</Grid>
Его ViewModel CalcViewModel.cs
public class CalcViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
//ctor
public CalcViewModel()
{
}
//Properties
public IMainWindowCodeBehind CodeBehind { get; set; }
private string _InputCalc;
public string InputCalc
{
get { return _InputCalc; }
set
{
_InputCalc = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(InputCalc)));
}
}
//Commands
private RelayCommand<string> _NumberInputCommand;
public RelayCommand<string> NumberInputCommand
{
get { return _NumberInputCommand = _NumberInputCommand ??
new RelayCommand<string>(OnNumberInput); }
}
private void OnNumberInput(string number)
{
InputCalc += number;
}
/// <summary>
/// Нажатие кнопки ОК во фрейме калькулятор
/// </summary>
private RelayCommand _CalcOkCommand;
public RelayCommand CalcOkCommand
{
get
{
return _CalcOkCommand = _CalcOkCommand ??
new RelayCommand(OnCalcOk);
}
}
private void OnCalcOk()
{
CodeBehind.CloseCalcPage();
}
}
Окно программы MainWindow.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Leak Size:" />
<TextBox x:Name="textBoxLeakSize" Text="{Binding LeakSize}"
Width="150" Margin="10,0"
GotFocus="textBox_GotFocus"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Other Size:" />
<TextBox x:Name="textBoxOtherSize" Text="{Binding OtherSize}"
Width="150" Margin="10,0"
GotFocus="textBox_GotFocus" />
</StackPanel>
</StackPanel>
<Frame x:Name="frame" Grid.Column="1" Margin="10" BorderBrush="LimeGreen" BorderThickness="2"
NavigationUIVisibility="Hidden" />
</Grid>
Его ViewModel MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
//ctor
public MainViewModel()
{
}
//Properties
private string _LeakSize;
public string LeakSize
{
get { return _LeakSize; }
set
{
_LeakSize = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(LeakSize)));
}
}
private string _OtherSize;
public string OtherSize
{
get { return _OtherSize; }
set
{
_OtherSize = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(OtherSize)));
}
}
}
Ну и самое главное - кодбихайнд окна программы
public interface IMainWindowCodeBehind
{
void CloseCalcPage();
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, IMainWindowCodeBehind
{
//
private MainViewModel _MainVM;
private CalcViewModel _CalcVM;
private TextBox _ActiveTextBox;
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
//загрузка вьюмодел, установка контекста данных
_MainVM = new MainViewModel();
this.DataContext = _MainVM;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
//пустая страница во фрейм
this.frame.NavigationService.Navigate(new Uri("PageEmpty.xaml", UriKind.Relative));
}
/// <summary>
/// Событие получения фокуса каким-то TextBox
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void textBox_GotFocus(object sender, RoutedEventArgs e)
{
//ссылка на редактируемый textbox
_ActiveTextBox = sender as TextBox;
//выключаем
textBoxLeakSize.IsEnabled = false;
textBoxOtherSize.IsEnabled = false;
//загрузка страницы и ее вьюмодел, установка контекста данных
PageCalc page = new PageCalc();
_CalcVM = new CalcViewModel();
page.DataContext = _CalcVM;
//даем ссылку на этот кодбихайнд, чтобы можно было вызвать CloseCalcPage()
_CalcVM.CodeBehind = this;
this.frame.NavigationService.Navigate(page);
}
/// <summary>
/// Метод вызываемый из CalcViewModel
/// Закрытие страницы калькулятора
/// </summary>
public void CloseCalcPage()
{
//включаем
textBoxLeakSize.IsEnabled = true;
textBoxOtherSize.IsEnabled = true;
if (_ActiveTextBox != null)
{
if (_ActiveTextBox.Name == textBoxLeakSize.Name)
{
//изменяем значение, через свойство вьюмодел
_MainVM.LeakSize = _CalcVM.InputCalc;
}
else
{
_MainVM.OtherSize = _CalcVM.InputCalc;
}
}
_ActiveTextBox = null;
_CalcVM = null;
this.frame.NavigationService.Navigate(new Uri("PageEmpty.xaml", UriKind.Relative));
}
}
Обратите внимание, что при нажатии на ОК в калькуляторе происходит вызов метода CloseCalcPage() из кодбихайнд окна программы.
P.S. класс RelayCommand или еще можно встретить в интернетах DelegateCommand это вариации на тему реализации интерфейска ICommand
вот например можете взять и использовать эту
public class RelayCommand : ICommand
{
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public RelayCommand(Action executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public RelayCommand(Action executeMethod, Func<bool> canExecuteMethod)
{
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null)
{
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter)
{
if (_TargetExecuteMethod != null)
{
_TargetExecuteMethod();
}
}
#endregion
}
public class RelayCommand<T> : ICommand
{
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public RelayCommand(Action<T> executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public RelayCommand(Action<T> executeMethod, Func<T,bool> canExecuteMethod)
{
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
T tparm = (T)parameter;
return _TargetCanExecuteMethod(tparm);
}
if (_TargetExecuteMethod != null)
{
return true;
}
return false;
}
// Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command
// Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter)
{
if (_TargetExecuteMethod != null)
{
_TargetExecuteMethod((T)parameter);
}
}
#endregion
}