I have a Window, which contains a Button AddParameter.
This Button has an Event called Button_Click.
Staying within the MVVM pattern, is it allowed to open a new window with a simple Button_Click? As far as I understood it, the code-behind of the View still counts as View:
private void Button_Click(object sender, RoutedEventArgs e) {
AddParameterWindow addParamWindow = new AddParameterWindow();
addParamWindow.Show();
}
Doing that with ICommands seems rather unnecessary, so I wanted to know if this would still count as a clean MVVM solution.
I don't think there is anything at all wrong with opening a window from another window in MVVM. The MVVM pattern is about separation of concerns in terms of ViewModels (and underlying models) being represented in any way necessary without it knowing anything about the View (see here for a good intro).
However, I think you have to ask yourself if making a new Window is really a good feature. Have you seen applications spawn another Window, and do you like that behavior? Have you given popups a thought which can look like Windows and can bind to the same ViewModel as the Window or UserControl it is logically under? Personally I avoid instantiating new Windows because I can centralize things that I want to appear in every View, like Styles, timeout Timers, etc.
You can ofcourse use the event Button_Click to open a new window, but that is now out of MVVM.
This maybe not right or good practice with MVVM, but this is how I do it:
assuming you have a ViewModelBase.cs that is something like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I have a DelegateCommand.cs that extends ICommand:
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged { add { } remove { } }
#pragma warning restore 67
}
Now in your SampleViewModel.cs:
public class SampleViewModel : ViewModelBase
{
public SampleViewModel()
{
}
public ICommand OpenWindowCommand
{
get { return new DelegateCommand(OpenSampleWindow); }
}
private void OpenSampleWindow()
{
var sampleWindow = new SampleWindow();
sampleWindow.Show();
}
}
Now in your View you can now bind your command to your button:
<Button Command="{Binding OpenWindowCommand}"/>
Related
I'm having difficulties with getting a bound textbox to update. I'm still new to WPF development and I must be missing a fundamental concept as I've read nearly everything available on the internet at this point and I'm still confused. Below is my code. First, an overview of what I'm doing to better set the context for my question.
Mainwindow is a Window that contains tabs that load various pages using frame source tags. I believe this might be causing me issues as I'm not sure where the actual object is getting instantiated for each tab, just that the XAML is being loaded.
Scratchpad is a class that contains a textbox, which is going to be updated and used by almost all classes that perform any type of operation to report status and any errors.
Textbox XAML (this is in "ScratchPad_View.xaml" for context)
<TextBox x:Name="scratchMessage"
Text="{Binding Path=ScratchMessage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Right"
Height="300"
Width ="500"
TextWrapping="Wrap"
VerticalAlignment="Top"/>
Code behind XAML
public partial class ScratchPad : Page
{
public ScratchPad()
{
InitializeComponent();
ScratchPad_Model ScratchPad_Model = new ScratchPad_Model();
this.DataContext = ScratchPad_Model;
}
}
Model Implementation
class ScratchPad_Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string _scratchMessage;
public string ScratchMessage;
{
get
{
return _scratchMessage;
}
set
{
if (value != _scratchMessage)
{
_scratchMessage = value;
OnPropertyChanged("ScratchMessage");
}
}
}
// Create the OnPropertyChanged method to raise the event
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Most of this I have cobbled together via responses to other questions on StackOverflow and reading numerous databinding tutorials however it's still not clicking. I'm not sure how to update the contents of the textbox and since I'm loading the page that contains the textbox in the XAML of my mainwindow I'm not sure I'm even referencing the correct object. The mainwindow loads this page in a frame tag, copied below.
<Frame Source="Common/View/ScratchPad_View.xaml" ></Frame>
In the code behind for this XAML, I have the following.
public partial class MainWindow
{
// Create scratchpad object for logging and status display
ScratchPad scratchPad = new ScratchPad();
public MainWindow()
{
InitializeComponent();
}
private void StartVault(object sender, RoutedEventArgs e)
{
// Creates the authentication prompt view object and pass the scratchPad reference for reporting
authPrompt_View _authPrompt_View = new authPrompt_View(scratchPad);
}
}
I pass the reference to the ScratchPad object that I created in the initialization of the mainwindow to all classes so that they can update the contents of the textbox, however I haven't had much luck in getting the binding to work. Once it works, I'm still not quite sure how I'm supposed to append text to the textbox. There's probably a great deal of problems here but I'm hoping to fix some of my conceptual problems and get a better understanding of what I'm doing wrong, thanks in advance!
You can use Application.Properties to set global properties for your project. So probably in SETTER method of textbox bound variable (in your case ScratchMessage), you need to set property in global application properties collection.
Below links explains it very well:
https://msdn.microsoft.com/en-us/library/aa348545(v=vs.100).aspx
http://www.c-sharpcorner.com/Resources/842/application-properties-in-wpf-and-c-sharp.aspx
My understanding is that , You have created the ViewModel for ScratchPad inside the constructor and assigning the DataContext in the same.
So, other windows will not have access to the DataContext.
My suggestion is that Maintain a base ViewModel class and inherit the base Viewmodel in all other ViewModel's.
Add ScratchMessage property inside base viewModel.
So you can access the ScratchMessage property from other viewModel's too.
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _scratchMessage;
public string ScratchMessage
{
get { return _scratchMessage; }
set
{
_scratchMessage = value;
this.OnPropertyChanged("ScratchMessage");
}
}
// Create the OnPropertyChanged method to raise the event
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ViewModel1 : BaseViewModel
{
ViewModel1()
{
this.ScratchMessage = "Message";
}
}
I have a scenario in wpf +mvvm i.e if my particular property changes in viewmodel1 then i wan to notify viewmodel2 having observable collection that property "A" has bee changed
1)I want to fire it for particular property not for all.
i have tried below code but not working .please let me know how cam i do this.
public class Model1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Create custom event
public event EventHandler NotifyChange;
private string testProperty;
public string TestProperty
{
get
{
return testProperty;
}
set
{
testProperty = value;
// If changing properties, fire your OnPropertyChanged to update UI
OnPropertyChanged("TestProperty");
}
}
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
// Fire your custom event if a property changed
NotifyChange(this, null);
}
}
}
public class Model2 : INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public Model2()
{
// Assuming there is an accessible instance of model1
Model1 m1Instance = new Model1();
// Hook up your NotifyChange event from model1
m1Instance.NotifyChange += Model1Changed;
}
private void Model1Changed(object sender, EventArgs e)
{
// this will be triggered on change in model1
}
private void OnCollectionChanged(object singleObject)
{
if (CollectionChanged != null)
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, singleObject));
}
}
Use PubSub Events
My suggestion would be to look into PubSub events.
My recommendation of doing this, is to use Prism. Here's some more information: http://www.c-sharpcorner.com/UploadFile/5ffb84/prism-event-aggregator-in-wpf-with-mvvm/
You will be sticking to proper MVVM practices in this case.
Here's MSDN's ever-useful guide: https://msdn.microsoft.com/en-us/library/ff649664.aspx
Have a really good read-up on how this works, and how to use/implement it.
Alternatively
This will work, but I would still defer to using PubSub events if possible.
You could try this:
public class Model1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _property;
public string Property
{
get { return _property; }
set
{
_property = value;
OnPropertyChanged("Property");
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Model2
{
public Model2()
{
// You might be storing your Model1 as a property in the Model2?
// I don't know, but I've put it in the constructor just for example.
var model1 = new Model1();
model1.PropertyChanged += OnModel1PropertyChanged;
}
private void OnModel1PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Property")
{
// Do stuff here when the right property has changed in model 1
}
}
}
I have only new'd up a Model1 in the constructor of Model2 as an example - you might be assigning it and storing as a field or property elsewhere in the Model2 ViewModel.
This might be particularly useful if you have ViewModels within ViewModels (parent VM > child VMs).
I use parent > child VMs quite regularly, and I don't think it's against MVVM best practices, but I still use the EventAggregator, instead of events.
As a side note, if you are using C#6:
Use nameof(Property) instead of "magic strings" (e.g. "Property"). This makes for much easier refactoring and compiler can tell you about errors - but essentially does the same job. Use this in the OnPropertyChanged() call in your setter
You can also use the nameof keyword when checking the property name, with the same principle as above. Like this: if (e.PropertyName == nameof(Model1.Property)) ...
Use null propagation: PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));. Changes the method body of your OnPropertyChanged to a nice one-liner, whilst still doing the same job
I digress
I would always look at sticking to proper MVVM practices, where possible.
I use Prism's EventAggregator on a daily basis and will swear by it.
Have a good read up on PubSub Events (you can use any type of event aggregator, but I would say to use Prism's, preferably)
Hope this helps! :)
If this is just to notify Model2 I think you dont need all these implementations. You can do something like
public string TestProperty
{
get
{
return testProperty;
}
set
{
testProperty = value;
// If changing properties, fire your OnPropertyChanged to update UI
OnPropertyChanged("TestProperty");
//Here you can call a method of Model2 sating that its changed
Model2 m2Instance = new Model2();
m2Instance.ValueChanged();
}
}
Add the method ValueChanged in you model 2.
That's because you're not registering to PropertyChanged. You're registering your event handler on NotifyChange so that PropertyChanged in Model1 is NULL and so NotifyChange is not fired.
So, you need to implement your OnPropertyChanged as the following:
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
if (NotifyChange != null)
NotifyChange(this, null);
}
Finally, if you want your NotifyChange to be fired for a specific property, then adjust the code above to check for the propName before you fire the event.
I have been doing tons of research using MVVM (Model View ViewModel) with WPF. I am developing a desktop application. This application consists of a main window. This main window has some buttons which do something. Also, there is a button that opens a OpenFileDialog box.
Currently, this is my ViewModel to which the main window binds to:
MainWindowPresenter Class
namespace BMSVM_Simulator.ViewModel
{
class MainWindowPresenter : ObservableObject
{
private bool logLoaded; // true if a log is currently loaded, false otherwise
public MainWindowPresenter()
{
logLoaded = true;
}
public ICommand load_data_button_pressed
{
get { return new DelegateCommand(doLoadData); }
}
private void doLoadData()
{
// DO LOAD DATA COMMANDS
}
public ICommand exit_button_pressed
{
get { return new DelegateCommand(doExit); }
}
private void doExit()
{
// DO EXIT COMMANDS
}
}
}
QUESTION 1: I am concerned that this is the "wrong" implementation. Is it correct (per MVVM) for each button to have a property of type ICommand and then a corresponding method implementing the functionality? A main window with a lot of buttons would have a very large ViewModel class, no?
QUESTION 2: If one of the buttons was a File->Open File button. So, in that case it would open up a new OpenFileDialog window. Would this be done in the same way I previously done it above (i.e. have a public ICommand open_file_dialog_button_pressed property and a corresponding public void doOpenFileDialog() method? This seems like I am mixing the "view" of the open file dialog into the ViewModel, although the view is already defined by the built in wpf OpenFileDialog class.
QUESTION 3: Is it true that each "view" of our application should have only a single "presenter" class (which is part of the ViewModel) to which that view binds to? In the example above, my main window view binds to only the MainWindowPresenter class. If I were to make another view (say a graph generated with Microsoft's Dynamic Data Display library in it's own popout window), I would need an additional "presenter" class in my ViewModel, correct?
Thank you very much!
Rich
For reference, I've included these classes, but they may not be useful:
DelegateCommand Class
namespace BMSVM_Simulator.ViewModel
{
public class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action)
{
_action = action;
}
public void Execute(object parameter)
{
_action();
}
public bool CanExecute(object parameter)
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore 67
}
}
ObservableObject Class
namespace BMSVM_Simulator.ViewModel
{
class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
//basic ViewModelBase
internal void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
}
}
1) Yes that's correct. You need to create a command property for each command. But thanks to your relay command you don't need to implement it directly. To prevent your ViewModel from busting i would recommend to move all commands into a separate CommandsViewModel serving as command source. Your View then binds to it.
2) Opening the Dialog can be achieved in XAML via routed commands using the CommandBinding property. So the tasks remains in the view. You basically try to avoid the dependency on any view related object. .NET provides some ready to use commands for common purposes (MSDN - ApplicationCommands)
3) You can share ViewModels among Views of course. That's one reason you structure your implementation into Model View ViewModel to be independent from changes and for reusability. It can become critical when more than one view is updating the same source simultaneously.
There are some similar questions on SO, but they weren't quiet the same, so I'm posting this instead. I'm new to MVVM, so I'm trying to figure out how I can create a class that can hold properties that can be shared among views. So, if I set a property in one view, all the other views would get notified if its changed and would adjust their properties accordingly.
What I have now is rather very crude and is definitely not something I want to use. This is my common class that will hold all the properties:
public static class Common
{
private static string _title;
public static string Title
{
get { return _title; }
set
{
if (_title == value)
{
return;
}
_title = value;
OnPropertyChanged("Title");
}
}
public static void Load()
{
// set properties here
}
public static event PropertyChangedEventHandler PropertyChanged;
private static void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(typeof(SettingsWorker), new PropertyChangedEventArgs(name));
}
}
}
...and I have to subscribe to it from each ViewModel:
Common.PropertyChanged += Common_PropertyChanged;
private void Common_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Title = Common.Title;
}
But this is where the breakdown happens. I can get the property name from the PropertyChangedEventArgs, but I've no idea how to get the value. Therefore, I'm forced to update all the properties, and that can get nasty to maintain. The code is becoming a mess.
I'm basically trying to get properties that ViewModels can share. How can I accomplish this?
It looks like you just have some global data you want to show in multiple places. The most straightforward way to do this is to just make it like and normal ViewModel class and then make it available to each of your other ViewModels and expose it from them to bind to directly (rather than copying the property into each of them). You can do this using IOC, or make it available statically, more similar to how you have it now.
If you go the static direction, the key change you need to make is to use a singleton rather than a static class in order to allow property change notification to work. Bindings work with INPC on instances but not static classes. The Common class would look more like this:
public class Common : INotifyPropertyChanged
{
private static Common _instance = null;
protected Common()
{
}
public static Common GetInstance()
{
if (_instance == null)
_instance = new Common();
return _instance;
}
private string _title;
public string Title
{
get { return _title; }
set
{
if (_title == value)
return;
_title = value;
OnPropertyChanged("Title");
}
}
public void Load()
{
}
public virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventArgs ea = new PropertyChangedEventArgs(propertyName);
if (PropertyChanged != null)
PropertyChanged(this, ea);
}
public event PropertyChangedEventHandler PropertyChanged;
}
There are a lot of different ways you can then use this. Here's one of the more direct ones:
public class SomeViewModel : ViewModelBase
{
public Common CommonData { get; private set; }
public SomeViewModel()
{
CommonData = Common.GetInstance();
}
}
Then in XAML you can bind to the properties from the common class and get change notification, even across the different VM usages.
<TextBox Text="{Binding Path=CommonData.Title}"/>
There's also the option of making the singleton accessible as a property and binding to it directly from XAML using x:Static but that's a little different direction that what you were asking.
So if you have other views that want to get notified when a property changes I would assume you have a separate viewmodel for each of those views. In that case what you would be asking for is "How can viewmodels talk to other viewmodels?" For a good learning experience, I would recommend looking into the observer pattern. If you don't like that style, then I would recommend you look into using a MVVM Framework like "SimpleMVVM, Catel, or many others" (Just need to look some up). Then once you have that framework in you project, I would create a baseviewmodel class that all your viewmodels will inherit. Once that is done you can take advantage of the frameworks messenger system. Basically it would look something like:
public YourViewModel()
{
RegisterToReceiveMessages(MessageTokens.SomeToken, OnChangeCallYourMethodToAddress); //The MessageTokens is something you generally create ur self.
}
#region Notifications
void OnChangeCallYourMethodToAddress(object sender, NotificationEventArgs<SlideShowLocale> e)
{
SomeProperty = e.Data;
}
Then to send a message to "YourViewModel" From another ViewModel:
public AnotherViewModel
{
SendMessage(MessageTokens.SomeToken, new NotificationEventArgs(WhateverYouWantToSend));
}
So basically, by declaring the token you want, it then finds the viewmodel that is registered with that token and listens for any messages to come in.
A further question to Clemen's fine answer here: DataContext values in view code behind. If one used this approach, is it possible to detect property changes on the VM at this point? These are correctly implemented through INotifyPropertyChanged.
var viewModel = DataContext as MyViewModel;
//How would one detect a property change on viewModel?
//Tried viewModel.PropertyChange which doesn't fire.
I think you must be doing something wrong that you're not mentioning in your post. The following code works as expected and will print MyTestPropertyName to the Console window.
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new MyViewModel();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
MyViewModel viewModel = DataContext as MyViewModel;
viewModel.PropertyChanged += MyPropertyChangedEventHandler;
viewModel.NotifyPropertyChanged();
}
private void MyPropertyChangedEventHandler(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine(e.PropertyName);
}
}
public class MyViewModel : INotifyPropertyChanged
{
public void NotifyPropertyChanged()
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs("MyTestPropertyName"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
It should be noted that this is TERRIBLE design, and is only designed as a proof of concept, that you can indeed subscribe to events on the ViewModel in the code-behind.
You would need to either subscribe to the PropertyChanged event of each dependency property (I.e. the properties that implement INotifyPropertyChanged), or modify your MyViewModel class to raise an event from the setters of the properties (dependency or otherwise) that you are interested in being notified about, and then subscribe to the common event.