There is an application on WPF. On some pages there is a TextBox on clicking on which the form should appear like a calculator, where we can enter data, click on it with the OK button and the data and this form should be displayed immediately in the TextBox, as on a separate page of this "Calculator" to get the object of exactly the TextBox that was clicked?
It looks like a presentation

Element "Calculator" added to the page using Frame. Tell me I'm a newbie. Maybe I'm not doing everything right?

  • What MVVM framework do you use? - Raider
  • I have no idea, at work they gave the task to do it, they gave a half-written project - Ivan Prodaiko

2 answers 2

If you do not use any Framework, then the program window itself or the currently active UserControl may well act as an intermediary. I give an example where the codecode program window acts as an intermediary.

Example of the program

Type calculator :) 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> 

Its 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(); } } 

The program window 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> 

Its 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))); } } } 

And the most important thing is the codebook program window

 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)); } } 

Please note that when you click on OK in the calculator, the CloseCalcPage() method is CloseCalcPage() from the code window of the program.

PS RelayCommand class or else you can find it on the Internet. DelegateCommand is a variation on the implementation of the ICommand interface. For example, you can take and use this

 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 } 
  • and how does CalcViewModel.cs implement the RelayCommand class? - Ivan Prodaiko
  • @IvanProdaiko see PS I added the class RelayCommand - Bulson

Usually, within the MVVM pattern, each TextBox associated with the corresponding string property in the View Model, and that is enough. But your TextBox has an additional opportunity - they can call a calculator on a click. Therefore, each TextBox must have its own View Model with text and a command to call a calculator.

The calculator, in turn, has its own View Model with the Text property and the OkCommand and CancelCommand , not counting the rest.

The question arises - and how, in fact, to establish communication between the two View Model, as well as the View Model and View, without creating hard links between the classes? For this, the Mediator pattern is used. In my example, I use the MVVM Light Toolkit library and its Messenger class.

As a result, the View Model for text fields looks like this:

 public class TextBoxViewModel : ViewModelBase { private string _text; public TextBoxViewModel() { ShowCalculatorCommand = new RelayCommand(ShowCalculator); } public string Text { get { return _text; } set { Set(ref _text, value); } } public ICommand ShowCalculatorCommand { get; } private void ShowCalculator() { MessengerInstance.Send(new NotificationMessage<string>(Text, "ShowCalculator")); // Π‘Π»ΡƒΡˆΠ°Π΅ΠΌ Π½ΠΎΡ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΡŽ "SubmitCalculator". MessengerInstance.Register<NotificationMessage<string>>(this, message => { if (message.Notification == "SubmitCalculator") { Text = message.Content; } }); // Π‘Π»ΡƒΡˆΠ°Π΅ΠΌ Π½ΠΎΡ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΡŽ "CloseCalculator". MessengerInstance.Register<NotificationMessage>(this, message => { if (message.Notification == "CloseCalculator") { // ΠšΠ°Π»ΡŒΠΊΡƒΠ»ΡΡ‚ΠΎΡ€ Π·Π°ΠΊΡ€Ρ‹Ρ‚ - отписываСмся ΠΎΡ‚ Π½ΠΎΡ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΉ. MessengerInstance.Unregister(this); } }); } } 

Please note that in the ShowCalculatorCommand command ShowCalculatorCommand we generate the "ShowCalculator" notification and attach the initial text to it. We also subscribe to the "SubmitCalculator" and "CloseCalculator" notifications in order to accept the text changed by the calculator and unsubscribe from the notifications after the calculator is closed, respectively.

The "ShowCalculator" notification will be processed by the corresponding View, which is creating new windows / frames (hereinafter I use separate windows, but in your case - these will be separate frames):

 // Π’ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ΅ события Loaded Π³Π»Π°Π²Π½ΠΎΠ³ΠΎ ΠΎΠΊΠ½Π°. Messenger.Default.Register<NotificationMessage<string>>(this, message => { if (message.Notification == "ShowCalculator") { var window = new CalculatorWindow { DataContext = new CalculatorViewModel { Text = message.Content } }; window.Show(); } }); 

In turn, CalculatorWindow should intercept the "CloseCalculator" notification and close itself:

 // Π’ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ΅ события Loaded ΠΎΠΊΠ½Π° с ΠΊΠ°Π»ΡŒΠΊΡƒΠ»ΡΡ‚ΠΎΡ€ΠΎΠΌ. Messenger.Default.Register<NotificationMessage>(this, message => { if (message.Notification == "CloseCalculator") { Close(); } }); 

And finally, the View Model calculator:

 public class CalculatorViewModel : ViewModelBase { private string _text; public CalculatorViewModel() { OkCommand = new RelayCommand(Ok); CancelCommand = new RelayCommand(Cancel); } public string Text { get { return _text; } set { Set(ref _text, value); } } public ICommand OkCommand { get; } public ICommand CancelCommand { get; } private void Ok() { MessengerInstance.Send(new NotificationMessage<string>(Text, "SubmitCalculator")); MessengerInstance.Send(new NotificationMessage("CloseCalculator")); } private void Cancel() { MessengerInstance.Send(new NotificationMessage("CloseCalculator")); } // ... (ΠΎΡΡ‚Π°Π»ΡŒΠ½Π°Ρ Π»ΠΎΠ³ΠΈΠΊΠ° ΠΊΠ°Π»ΡŒΠΊΡƒΠ»ΡΡ‚ΠΎΡ€Π°) } 

I think the View markup is quite trivial, so I leave it behind you.