Using a Button_Click event to change textbox content using MVVM [closed] - c#

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I don't even know if that's the right title for this but anyway. I'm switching from WinForms and trying to learn WPF and the MVVM methodology.
I have a custom class, Incident, used to store data about incidents that occur that my team must respond to. I am building a View to display the data in instances of that class to a user, as well as allow that user to manipulate it. There are several pieces of DateTime data that need to be displayed - Start, End, Notification_Received, Actions_Taken. I need to have a small button that will put DateTime.Now into each associated TextBox as well as change the underlying value of the currently loaded instance of the Incident class.
I'm trying to figure out how to do this. With WinForms, I would've just set TextBox.Text and Incident.Start (etc) to DateTime.Now all in the same Button_Click function, but my understanding of MVVM is that I'm not supposed to do this, instead I should bind the TextBox to the VM and update the value of the VM.
This is where I'm stuck. I'm pretty sure I'm good on how to do the binding, but not the part where I change the value of the VM from my Button_Click function. Please assist?

You're correct - the view-model should control the change, and the textbox should update through a binding.
In the MVVM pattern, code-behind is rarely used. Instead of a Button_Click method, you need a command binding:
<Button Command="{Binding SetAllDatesToNowCommand}"/>
The command will be executed when the button is pressed. SetAllDatesToNowCommand is a command handler - it should be an ICommand property on your view-model:
public ICommand SetAllDatesToNowCommand { get; private set; }
I generally tend to use RelayCommand from the MVVM Light Toolkit to create command handlers, because the syntax is clean and very simple. The command handler is initialized in your view-model's constructor. The handler method passed to RelayCommand is where you should set properties on the selected Incident object:
public YourViewModel()
{
this.SetAllDatesToNowCommand =
new RelayCommand(this.ExecuteSetAllDatesToNowCommand);
}
...
public void ExecuteSetAllDatesToNowCommand()
{
this.selectedIncident.Start = DateTime.Now;
// etc.
}
If the bindings on your textboxes are correctly set up, and the properties that are being set are firing appropriate PropertyChanged events, they should be updated when the properties are set in the command execution method.
However, I'd suggest that you should have a view-model for Incident, which implements the INotifyPropertyChanged interface. The command outlined above would be a property on that view-model. Setting, for example, the Start property on that view-model should set the property on the Incident object it is the view-model for (the "model" object), and should also raise a PropertyChanged event. Otherwise, your Incident class will have to implement INotifyPropertyChanged, and the line between model and view-model classes becomes less clear.

I assume you've bound the form to your ViewModel. Therefore you have a property in your ViewModel of Start. You want to bind a field to that
<TextBlock Text={Binding Start}/>
or
<TextBlock Text={Binding Incident.Start}/>
Depending on how you expose Incident. To update Start you have to do two things. Use a command on the button.
<Button Command="{Binding TestCommand}">Test</Button>
In your ViewModel you'd define the command.
private RelayCommand _testCommand;
public RelayCommand TestCommand
{
get
{
return _testCommand ?? (_testCommand = new RelayCommand(TestUpdate, CanRunTest));
}
set
{
if (_testCommand == value) return;
_testCommand = value;
}
}
public bool CanRunTest()
{
return some boolean test that defines if the command can run now;
}
private void TestUpdate()
{
Incident.Start = DateTime.Now;
}
RelayCommand is a helper method you can find in MVVMLight. Also see Josh Smith for info on RelayCommand.
Second, you will need to implement INotifyPropertyChanged on the Incident model or look up ObservableObject and make your Start property look like this.
public class Incident : ObservableObject
{
private ObservableCollection<WordLetter> _start;
public virtual ObservableCollection<WordLetter> Start
{
get { return _start; }
set
{
if (value == _start) return;
_start = value;
NotifyPropertyChanged();
}
}
}

Related

wpf CheckBox command parameter

In my View I have 8 checkboxes all bound to a different property in the Model, lets say bool port1 ... port8. The property changes when the concerned checkbox is clicked.
When the user checks or unchecks one of these checkboxes, I also execute a binded command 'SetPortCommand'. The command executes a function like SetPort(uint numPort, bool set)
My view looks like this:
<CheckBox x:Name="cbPort1" Content="port1" Command="{Binding SetPortCommand}">
<CheckBox.IsChecked>
<Binding Path="MyModel.Port1"/>
</CheckBox.IsChecked>
</CheckBox>
Behind the whole thing is quite slow hardware, so I would like to avoid calling the function SetPort() for each port. I could use 8 commands like SetPortCommand1 to SetPortCommand8, but that causes lots of duplicate code.
I had the following ideas, but I do not know how to implement it, neither which one wouldf be conform to MVVM.
idea would be to somehow pass a constant to the command, telling it which of the 8 ports it should check or uncheck. Is there a easy way to do this?
idea would be to somehow use the OnPropertyChanged()-Event to call the function in here.
Any help appreciated :-)
You could avoid exeucting the command and just call your method from each setter, e.g.:
private bool _port1;
public bool Port1
{
get { return _port1; }
set { _port1 = value; SetPort(); }
}
If the SetPort modifies the other properties, you could use a flag to determine whether it should be invoked:
private bool _setPort = true;
private bool _port1;
public bool Port1
{
get { return _port1; }
set
{
_port1 = value;
if (_setPort)
{
_setPort = false;
SetPort();
_setPort = true;
}
}
}
You could do the same thing in the Execute method of your command if you still want to use a command for some reason.
The _setPort field prevents the method from getting called for each source property.
Alternatively, you could also avoid setting the property in the method and instead set the backing field and raise the PropertyChanged event:
_port2 = true;
OnPropertyChanged(nameof(Port2));
This will bypass the setter and avoid calling the method.
With a checkbox, the usual approach is to bind ischecked and to act in that setter. This uses text rather than a bool, but you can see there's a method called in the setter. https://social.technet.microsoft.com/wiki/contents/articles/30564.wpf-uneventful-mvvm.aspx#Change_of_Variable
You have a repeating group of functionality in this set of check boxes.
The usual approach is to bind a list or observablecollection of viewmodels to the itemssource of an itemscontrol. Template that data out into your ui. Here you'd have checkboxes. Each of your row viewmodels would hold data about a port. Name and whatnot.
If you particularly want a command you could have an ICommand such as delegatecommand or relaycommand bound from either:
The row viewmodel - in which case that "knows" which port is to be acted on. And your code that does stuff with that port is in the row viewmodel.
or
To the window viewmodel, in which case the row viewmodel is the datacontext of the button and this is passed as a parameter to the ICommand.
Both these are common scenarios working with buttons and you should be able to find code googling.
Binding IsChecked would be simpler and is hence more usual though.

How to instantiate a dialog and record user input while MVVM pattern is used to create dialog in WPF

I am a C++ developer and new to WPF and MVVM. please bear with me if I choose any wrong word to ask my question
I have my Main application in MFC/C++ which is passing some data to C# library(CLI is used as middle layer).
In C# library, there is a section of code where a dialog is opened , data is filled and user selection is notified to the calling object in below way -
public classA()
{
MyDialog dlg = new MyDialog(param1, param2, param3)
if(dlg.ShowDialog().GetValueOrDefault())
{
var name = dlg.name;
var roll = dlg.roll;
}
else
{
var name = string.Empty;
var roll = string.Empty;
}
}
Now Dialog has been modified and implemented using MVVM pattern.
I have created below files as part of implementation-
1
MyDialogView.Xaml
MyDialogView.xaml.cs
MyDialogViewModel.cs
MyDialogModel.cs
My question is, how to instantiate the new dialog now from my classA so that data is filled using the parameters passed to dialog in same way as previously it was doing and record user selection without loosing any data and safely closing the view.
Standard MVVM approach works like this (at least when using MVVM Light):
You have a VM layer, a Class Library.
You have a View layer, a WPF Controls Library or WPF Application.
View layer adds reference to VM layer. VM layer doesn't know anything about View.
You create a normal public class for your dialog's VM. Call it DialogVM or whatever. Make sure this class inherits from MVVM Light's built-in ViewModelBase. This will get you access to change notification methods provided by MVVM Light. Might look like this in your case:
public partial class DialogVM : ViewModelBase
{
private string _Name;
public string Name
{
get { return _Name; }
set { Set(ref _Name, value); }
}
private string _Roll;
public string Roll
{
get { return _Roll; }
set { Set(ref _Roll, value); }
}
}
VM layer has a global static class called ViewModelLocator. This class performs IoC/DI and provides public static properties to expose different VMs. (In your case your dialog's VM goes to the VM project and the ViewModelLocator looks something like this:
using System;
namespace VMLayer
{
public class ViewModelLocator
{
static ViewModelLocator()
{
SimpleIoc.Default.Register<DialogVM>(true);
}
public static DialogVM MyDialog => SimpleIoc.Default.GetInstance<DialogVM>();
}
}
Your dialog box (a Window) goes to View layer and uses this exposed property MyDialog to provide a DataContext for the dialog:
<Window x:Class="GlasshouseTestingShell.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:vm="clr-namespace:VMLayer;assembly=VMLayer"
DataContext="{x:Static vm:ViewModelLocator.MyDialog}"
d:DataContext="{d:DesignInstance Type=vm:DialogVM}">
</Window>
Look how cleanly we have created View layer's DataContext without writing a line of C# code in the View layer. This is also elegant in the sense that you get all design-time Intellisense in Binding expressions and elsewhere.
You now bind all your UI stuff (textboxes, buttons etc.) to the public properties and commands exposed by your dialog's VM. Still no lines in the code-behind. Might look like this in your case:
<TextBox Text="{Binding Name}" />
Rest of the stuff is in C++:
You add reference to your View and VM DLLs in your C++ project.
Create an object of your dialog. It will automatically instantiate its VM and perform binding. You call ShowDialog() to bring it to screen.
Use takes actions in the dialog and finally presses OK or Cancel.
You capture dialog result and then access your dialog object's DataContext property, which is an object of DialogVM class. You can access user-supplied values from therein as Binding has updated those properties for you in the VM.
I'm not sure I follow all of your requirements but this is roughly how I'd approach such a task:
Instantiate the view and viewmodel in class A.
Set whatever parameters you want on your viewmodel. Either as properties or via constructor injection.
Set the datacontext of the view to the viewmodel.
Everything you need to bind should then bind between them.
showdialog the view.
The user edits in the view and changes persist to the viewmodel properties.
They finish editing and you then work with the viewmodel properties. Maybe one of them is the model you mention. Maybe the model is instantiated by the viewmodel to get data or by classA if that is more convenient. In the latter case you probably have to pass that model to the viewmodel.
Bearing in mind the above.
Some rough code:
public class ClassA
{
MyDialogViewModel vm = new MyDialogViewModel { Name = "X", Roll = "Y" };
MyDialog dlg = new MyDialog();
dlg.ShowDialog();
var name = vm.Name;
var roll = vm.roll;
// Do something to persist your data as necessary. Either here or in a model within the viewmodel
}
Name and Roll presumably bind to some textboxes Text properties in the view or some such.
If it's as simple as obtaining two string values then I see no advantage to actually having a model at all. On the other hand, if processing is more involved then of course the viewmodel might instantiate a model.
MyDialogViewModel should implement inotifypropertychanged and anything you need to bind should be a public property. Not sure if you'll need propertychanged notification but always implement it. Optionally raise propertychanged from property setters.

Multiple views sharing same data with two-way data binding between multiple threads

UWP app ( mvvm architecture ) I have a MainView which has a collection in its ViewModel, used to bind to the GridView on MainView and each item has a TextBox with 2 way databinding with Description property of class Note.
Xaml of the TextBox of each gridviewitem.
<TextBlock Text="{x:Bind Description,Mode=TwoWay}"
Collection property used to bind to ItemSource of gridview.
public ObservableCollection<Note> Notes { get; }
and this is the class Note
public class Note : Observable
{
private string _description;
public string Description
{
get => _description;
set => Set(ref _description, value, nameof(Description));
}
}
the Observable class is for two way data binding help.
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Now everything uptil this point works perfectly, when I change the text in textbox, it changes the value of Description as well.
Second View
Now I have a feature where each GridViewItem has a button in it which opens the Note in new window. and this new window has nothing but only 1 TextBox, so now the secondary view and the GridViewItem which opened that view are using the same object of Note.
This TextBox in secondary view also has 2 way data binding with the Description of the Note.
The Problem
What I want is that whether the textbox in gridview or the textbox on the secondary view is edited, the value of description must remain synced between these 2 textboxes, that is why I tried to bind them 2 way with same object of Note hence the same Description object is bound to both of them.
Error here was expected to me which was Marshalling threading error, so whenever I try to change value of any textbox, it tried to update UI on other view ( which is another thread ) which is ofcourse not allowed.
I know about CoreDisptcher
I already know about the Dispatcher feature of UWP for safe cross thread communication, I already have it all setup and if I use it from a normal method I can easily use it for cross thread UI update and it totally works. But my issue is the following line :
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\
Exception occurs when it tried to invoke the PropertyChanged I tried to wrap following line in my Dispatcher :
OnPropertyChanged(propertyName);
but INotify interface does not allow me to have a Set<> method which returns a Task instead it needs to return just an object, this is the point where I am stuck and I dont know how to make use of Dispatcher in this scenario, please let me know if there is some better way to do this, it seems this way might not be so efficient.
Thanks.
The best solution in this case would be to have a separate set of INotifyPropertyChanged instances for each window and using some kind of messaging solution like EventHub in MvvmLight, which publishes message that the underlying model changed and all interested parties should update their instances.
Another option would be to create a base model class, which maintains a dictionary of INotifyPropertyChanged instances for each UI thread (so it would be a Dictionary<Dispatcher, YourModelClass>. Now the parent would subscribe to PropertyChanged event of each child instance and once it executes would propagate the event to other childs using the appropriate Dispatcher.
Also there is a very interesting utility class ViewSpecificBindableClass by Marian Dolinský on his GitHub which could potentially be a solution that would allow you to have "single" class in multiple views, aware of multiple dispatchers. I haven't tried it yet, but it seems promising.
So I finally had to take a totally different approach centralizing TextChanged events of MainView textbox and the one on the secondaryview.
I essentially passed the textbox on the mainpage through to the secondary page ( secondary view ) and then subscribed to its TextChanged event. I also subscribed to the TextChanged event of textbox on the secondary view, and then with help of reverse dispatchers I was able to sync the text between 2 windows without any problems.
Note : always make sure to unsubscribe to events when the secondary window closes to prevent memory leaks.
private async void PipBox_TextChanged(object sender, TextChangedEventArgs e)
{
string text = PipBox.Text;
await CoreApplication.MainView.Dispatcher.AwaitableRunAsync(() =>
{
if (parentBox.Text != text)
parentBox.Text = text;
});
}
private async void ParentBox_TextChanged(object sender, TextChangedEventArgs e)
{
string text = parentBox.Text;
// the awaitablerunasync extension method comes from "Windows Community Toolkit".
await _viewLifetimeControl.Dispatcher.AwaitableRunAsync(() =>
{
if (ViewModel.MyNote.Description != text)
ViewModel.MyNote.Description = text;
});
}
Notice that I still have 2 way data binding on both textboxes and it does not cause any exceptions because I am using 2 different instances of Note for both views.
<TextBox Text="{x:Bind ViewModel.MyNote.Description, Mode=TwoWay}"
x:Name="PipBox"/>
but because I have twoway data binding on both textboxes, that is how I can easily keep both instances of Note in sync as well on separate threads.
I will keep the github repo in case it can help anyone else : https://github.com/touseefbsb/MultiWindowBindingSync
P.S : A special thanks to Martin Zikmund who helped me a lot in figuring out this solution.

wpf mvvmlight passing data to viewmodel

I have the following views and viewModels View: Staff, VM: StaffViewModel and View: Notes, VM: NotesViewModel.
My StaffViewModel has a SelectedStaffMember property of type SelectedEmployee.
The Staff view has a button that launches another view (Notes).
When the user enters a note I need to save it against the SelectedEmployee, which means the NotesViewModel needs to know the currently selected employee ID.
Right now Im doing this via my ViewModelLocator but this seems wrong, what is the corret way to pass data to a VM???
I'm using MVVM Light.
Relevant code - StaffViewModel
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
if (value == _selectedEmployee) return;
_selectedEmployee = value;
HolidayAllowance = _staffDataService.GetEmployeeHolidayAllowance(_selectedEmployee.Id);
RaisePropertyChanged();
RaisePropertyChanged(nameof(HolidayAllowance));
}
}
NoteViewModel
public RelayCommand SaveNoteCommand { get; private set; }
private void SaveNote()
{
var note = new Note
{
NoteContent = NoteContent,
EmployeeId = ViewModelLocator.Staff.SelectedEmployee.Id,
NoteDate = NoteDate
};
_dataService.SaveNote(note);
}
I'm using MahApps Flyouts to show the view for add note:
This is where the view is shown, it is launched from MainView.xaml NOT Staff.xaml, which I think is going to be another issue of getting SelectedEmployee ID:
MainView.xaml
<controls:Flyout Name="AddNoteFlyout"
Header="Add Note"
IsModal="True"
IsOpen="{Binding IsAddNoteOpen}"
Opacity="85"
Position="Right"
Width="450">
<views:AddNote VerticalAlignment="Top" Margin="0,30,0,0"/>
</controls:Flyout>
Im considering firing a message on the button click that launches the View, which my staff view would register against. The message would contain the selectedEmployeeId. Would that be a better way?
The simple way
The simple way is what you are doing, but maybe a bit better solution is to create a static or singleton class like a NavigationParameterContainer and store the selected StaffMember in a public property. Then you can retrieve it in your NotesViewModel
The best practice
The better solution for passing data between ViewModels is using a custom navigation service, and navigation aware ViewModels.
MVVMLight don't support this, so either you use a different framework like Prism or write yourself an architecture that you can use for making parameterized navigationt.
The base idea is that you create an INavigationAware interface that support navigation lifecycle callbacks like OnNavigatedTo, which receives an object representing the NavigationParamter (the selected StaffMember).
Then you create some kind of NavigationService with a Navigate method, that accepts some parameter to determine the Page you want to navigate to, and an object wich is the NavigationParamter.
When you navigate you call the Navigate method on your Service and pass the selected item as parameter. Then you need to make the actual navigation inside your service, and after the navigation is finished, you call the OnNavigatedTo callback on your ViewModel if it is implementing the INavigationAware interface. (You can retreive the VM from the Page.DataContext and cast it to INavigationAware if it is not null you can call the OnNavigatedTo).
Finally in your NotesViewModel you just need to implement the INavigationAware interface, and handle the parameter you received in the OnNavigatedTo method.
This is just the basic idea but I strongly recommend you to see some MVVM framework that already implements this. (like PrismLibrary).

Hair loss and MVVM user controls

I have a user control written in C# & WPF using the MVVM pattern.
All I want to do is have a property in the bound ViewModel exposed to outside of the control. I want to be able to bind to it and I want any changes to the property to be picked up by anything outside the control that is bound to the exposed value.
This sounds simple, but its making me pull out my hair (and there is not much of that left).
I have a dependency property in the user control. The ViewModel has the property implementing the INotifyPropertyChanged interface and is calling the PropertyChanged event correctly.
Some questions:
1) How do I pick up the changes to the ViewModel Property and tie it to the Dependency Property without breaking the MVVM separation? So far the only way I've managed to do this is to assign the ViewModels PropertyChanged Event in the Controls code behind, which is definitely not MVVM.
2) Using the above fudge, I can get the Dependency property to kick off its PropertyChangedCallback, but anything bound to it outside the control does not pick up the change.
There has to be a simple way to do all of this. Note that I've not posted any code here - I'm hoping not to influence the answers with my existing code. Also, you'd probably all laugh at it anyway...
Rob
OK, to clarify - code examples:
usercontrol code behind:
public static DependencyProperty NewRepositoryRunProperty = DependencyProperty.Register("NewRepositoryRun", typeof(int?), typeof(GroupTree),
new FrameworkPropertyMetadata( null, new PropertyChangedCallback(OnNewRepositoryRunChanged)));
public int? NewRepositoryRun
{
get { return (int?)GetValue(NewRepositoryRunProperty); }
set
{
SetValue(NewRepositoryRunProperty, value);
}
}
private static void OnNewRepositoryRunChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != e.NewValue)
{
}
}
public GroupTree()
{
InitializeComponent();
GroupTreeVM vm = new GroupTreeVM();
this.DataContext = vm;
}
Viewmodel (GroupTreeVM.cs)
private int? _NewRepositoryRun;
public int? NewRepositoryRun
{
get
{
return _NewRepositoryRun;
}
set
{
_NewRepositoryRun = value;
NotifyPropertyChanged();
}
}
And now for my weekly "don't do that" answer...
Creating a ViewModel for your UserControl is a code smell.
You're experiencing this issue because of that smell, and it should be an indication that you're doing something wrong.
The solution is to ditch the VM built for the UserControl. If it contains business logic, it should be moved to an appropriate location in another ViewModel.
You should think of a UserControl as nothing more than a more complex control. Does the TextBox have its own ViewModel? No. You bind your VM's property to the Text property of the control, and the control shows your text in its UI.
Think of UserControls in MVVM like this--For each model, you have a UserControl, and it is designed to present the data in that model to the user. You can use it anywhere you want to show the user that model. Does it need a button? Expose an ICommand property on your UserControl and let your business logic bind to it. Does your business logic need to know something going on inside? Add a routed event.
Normally, in WPF, if you find yourself asking why it hurts to do something, it's because you shouldn't do it.
Perhaps I've misunderstood, but it seems like you're trying to use binding in the code behind?
public MyUserControl()
{
InitializeComponent();
// Set your datacontext.
var binding = new Binding("SomeVMProperty");
binding.Source = this.DataContext;
SetBinding(MyDependencyProperty, binding);
}

Categories