I have tabs application, each Tab contain many views... And in each Tab the save mode is global.
If I leave current tab containing changes, a pop-up appear asking user confirm with or without save or cancel. After save, others opened tabs is reloaded.
How would you do, to detect the changes? To save only the views changed? And then to propagate the changes to the other tabs?
My first think is, to implement a IsModified property and ReloadTab method in each ViewModel, but is not really viable solution, each setter must change this property... Any idea ?
class MyViewMode
{
...
public bool IsModified { get { return MyViewModel1.IsModified || MyViewModel2.IsModified ... || _isModified }
...
}
[EDIT]
I hesitate between EventAggregator approach and Messenger (MVVM Light Toolkit implementation)...
I think I am going to create Events/Messages Domain representing each command generates a tab reloading, and create a Handler (Service receive all messages/events and send specific messages/events for each View to be reloaded). Any advices ?
I've done this using a global event broker. The idea is that events such as save which have global scope will pass through this broker class.
The event broker is a singleton, where each class will register it's handlers. The handler would be registered using attributes:
[EventSubscribe(EventNames.Save)]
private void OnSaved(GlobalEventArgs args)
{
// do something on saved
}
And each object that wishes to push itself to the broker would do it like this:
EventBroker.Instance.Register(this);
How does this relate to your tabs issue? Fairly simply, when one tab saves, then that should raise the save event via the EventBroker:
EventBroker.Instance.Publish(
EventNames.Save,
new SavedGlobalEventArgs(typeof(YourModel)));
And all your other tabs would handle the event such as this:
[EventSubscribe(EventNames.Save)]
private void OnSaved(GlobalEventArgs args)
{
var savedArgs = (SavedGlobalEventArgs)args;
if(savedArgs.SavedType == typeof(YourModel)
{
this.Model.Refresh();
}
}
You'll still have to handle the saved event on each tab that might require a refresh when another tab has done something, but this keeps the code relatively nice and simple without having to put all kinds of crap in. Can also extend it outside of a save event, make some other global events that may be useful:
UserCreated
UserLoggedIn
SearchInitiated
whatever; i don't know the context of your app - but the broker is a really nice way to deal with sharing knowledge in a tabbed environment.
Please let me know if you want me to send some code :)
The best way to do this is by modifying each setter. If not, you cannot know exactly if your model data has changed and I wouldn't suggest tracking modified changes in the UI. Something like this should give you a good head start.
public class Person : IModifiable
{
private bool _markDirty;
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (value != _Name)
_markDirty = true;
_Name = value;
}
}
public bool IsDirty()
{
return _markDirty;
}
}
public interface IModifiable
{
public bool IsDirty();
}
Related
I'm developing using DevExpress XAF, my problem is a little bit tricky, in short, when I save my class I make changes in other classes based on the data provided, I realized this with a controller that executes code when I close the detail view, the problem is that there is a scenario that does not meet my needs, here is it:
When I open a detail view already existing and that I modify some data, when I close the window, the program displays a window of confirmation ("do you want to register?) when I click on no, normally my view controller will not do anything because I refused to change my class data
Finally here is my question: How to test in my view controller if the object was registered or not before proceeding to the change and execute my code?
When you say register, I think you mean save.
You can use the ObjectSpace.GetObjectsToSave() method to obtain a list of objects which will be saved when ObjectSpace.CommitChanges() is called. You can then determine whether View.CurrentObject is in this list.
Alternatively you could use the ObjectSpace_ObjectChanged event. Something like this.
public class MyViewController : ObjectViewController<DetailView, Contact> {
protected override void OnActivated() {
base.OnActivated();
ObjectSpace.ObjectChanged += ObjectSpace_ObjectChanged;
}
void ObjectSpace_ObjectChanged(object sender, ObjectChangedEventArgs e) {
if (e.Object == View.CurrentObject) {
// execute your business logic
}
}
protected override void OnDeactivated() {
base.OnDeactivated();
ObjectSpace.ObjectChanged -= ObjectSpace_ObjectChanged;
}
}
See here for a Support Center discussion of a similar request.
I have a MvxViewController and in the ViewDidLoad i bind the button click to the viewmodel. When the button is clicked I open another view in which I will need to return a string back to my first view
public override void ViewDidLoad ()
{
var set = this.CreateBindingSet<MyView1, MyView1ViewModel>();
set.Bind(myButton).To(vm => vm.MyButtonCommand);
set.Apply();
}
public ICommand MyButtonCommand
{
get
{
_myButtonCommand = _myButtonCommand ?? new MvxCommand(MyButtonCommandClick);
return _myButtonCommand;
}
}
private void MyButtonCommandClick()
{
ShowViewModel<ViewModelNumber2>();
}
After some logic is ran in my second view I want to return the string
private void SomeMethodInViewModelNumber2()
{
//Raise event that will get pickup up in MyView
//Or somehow get "SomeString"
if (OnMyResult != null)
OnMyResult ("SomeString");
}
The problem is that I don't want to send the string back using the messenger. I have my reasons but basically because ViewModelNumber2 can be opened from many different places and works slightly different and managing the different messages that would need to be sent back and where to subscribe to these messages would be a mess
Is there any way that I can do something like the below?
public override void ViewDidLoad ()
{
var set = this.CreateBindingSet<MyView1, MyView1ViewModel>();
set.Bind(myButton).To(vm => vm.MyButtonCommand).OnMyResult((myString) => {Process(myString)});
set.Apply();
}
Or perhaps when I create ViewModelNumber2 I should pass a callBack into the constructor and use that to send the string back from ViewModelNumber2 to MyView1ViewModel
ShowViewModel<ViewModelNumber2>(OnMyResult);
What is the best way to do this?
In short: I don't know what "the best way to do this" is.
The area of ChildViewModel-ParentViewModel messages is complicated - especially because on platforms like Android using Activities and WindowsPhone using Pages you have no guarantee that the ParentViewModel will be in memory when the Child is shown. (Note: this isn't a problem on iOS as its "app suspension" model is simpler)
When I do need one ViewModel returning data to another, then:
Often I try to implement the data collection views as "popup dialogs" rather than as "whole pages" - this makes the parent-child ViewModel relationship more correct - and ensures the parent ViewModel will be in memory when the child closes.
Often I recommend people use a Messenger-based technique like Greg describes in: http://www.gregshackles.com/2012/11/returning-results-from-view-models-in-mvvmcross/
often I've done this messaging via background services rather than via ViewModel-ViewModel messaging (a bit like the way screens are updated in https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/tree/master/N-17-CollectABull-Part6)
Another solution I've used is to:
implement a IDropBoxService singleton - with an API like void Deposit(key, value) and bool TryCollect(key, out value)
allow the closing "child" ViewModels to leave "values" when they close
implement IVisible functionality in my "parent" ViewModel - like in https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/master/N-42-Lifecycles/Lifecycle.Core/ViewModels/FirstViewModel.cs#L10
use the IVisible method to check for messages
To implement anything perfectly, you really should add serialisation code to make sure this all works during "tombstoning" on all platforms... but often this is overkill - for a simple data collection dialog users often don't need "perfect" tombstoning support.
I'm making a small demo application for MVVM with caliburn.
Now I want to show a MessageBox, but the MVVM way.
For dialogs I created an event, that is handled in the ShellView (the root view)
and just calls WindowManager.ShowDialog with a Dialogs ViewModel type.
Seems to stick to MVVM for me.
But what is the way to show a messagebox and get its result (Okay or cancel)?
I already saw this question, but it contains no answer either.
Mr Eisenberg hisself answers with
"Caliburn has services built-in for calling custom message boxes."
Can anyone tell what he means with that? I don't see it in the samples.
As you mentioned, you just prepare the view model (e.g. ConfirmationBoxViewModel) and an appropriate view. You'll have to create two actions (after inheriting the view model from Screen, which is necessary to use TryClose. You can always implement IScreen instead, but that would be more work):
public void OK()
{
TryClose(true);
}
public void Cancel()
{
TryClose(false);
}
and then in your other view model:
var box = new ConfirmationBoxViewModel()
var result = WindowManager.ShowDialog(box);
if(result == true)
{
// OK was clicked
}
Notice that after the dialog closes, you can access the view model properties if you need to pull additional data from the dialog (e.g. Selected item, display name etc).
In the article A Billy Hollis Hybrid Shell (written by the framework coordinator) the author showed a nice way to handle both dialog and message boxes, but he used dependency injection (you can go without DI of course but it makes things simpler). The main idea is that you can let your main window, the one used as the application shell implement an interface that looks something like this:
public interface IDialogManager
{
void ShowDialog(IScreen dialogModel);
void ShowMessageBox(string message, string title = null, MessageBoxOptions options = MessageBoxOptions.Ok, Action<IMessageBox> callback = null);
}
and then he registers this interface with the IoC container, I guess you can use your imagination from there on and if you don't have time then you can look at the source code that accompanies the article.
When the root/main/shell view-model implements a kind of DialogService interface, every other view-model needing to show dialogs will end up with a dependency on the root view-model. Sometimes this might not be desiderable, e.g. if it could cause a dependency loop:
DialogService (aka RootViewModel) -> SomeViewModel -> RootViewModel.
A more involved approach to break this dependency chain (and actually invert it) is the following:
Implement a behavior that detects Window.OnSourceInitialized event and attach it to main view Window component. That is the event fired when the window handle is available. Upon event, behavior will notify some handler passed in via attached property:
<my:WindowSourceBehavior InitListener="{Binding WindowListener}" />
public class WindowSourceBehavior : Behavior<Window>
{
// ...
// boilerplate code for IWindowListener InitListener dependency property
// ...
attachedWindow.SourceInitialized += (sender, evt) =>
{
// ...
InitListener.SourceInitialized(sender as Window);
}
}
DialogService exposes a handler - or interface - as requested by behavior:
public class DialogService : IWindowListener
{
// ...
public void SourceInitialized(Window rootWindow) { /* ... */ }
}
In root view-model, (indirectly) get the DialogService injected as a dependency. During construction, sets view-model bound property, WindowListener, to the DialogService handler/interface:
public MainViewModel(IWindowListener dialogServiceInDisguise)
{
WindowListener = dialogServiceInDisguise;
}
public IWindowListener WindowListener { get; private set; }
Doing so, the DialogService is able to get a hold of root Window, and whichever view-model needs to show a dialog does not create a(n indirect) dependency on main view-model.
I've been toying with where to put "are you sure?" type prompts in my MVVM WPF app.
I'm leaning towards thinking that these are purely part of the View. If the ViewModel exposes a DeleteCommand, then I would expect that command to delete immediately.
To integrate such prompts into the ViewModel, it would have to expose a separate RequestDeleteCommand, a DeletePromptItem property for binding the prompt against, and which could also double as a trigger to show the prompt.
Even with this, there's nothing stopping a unit test calling DeleteCommand directly, unless I put specific logic in the ViewModel to require DeletePromptItem to match the item supplied as an argument to DeleteCommand.
However, this all just seems like noise in the ViewModel to me. The prompt is more a user interface issue to guard against misclicks etc. To me this suggests it should be in the view with a confirmed prompt calling the DeleteCommand.
Any thoughts?
The prompts should definitely not be part of the ViewModel, but this doesn't necessarily mean that the best solution is to hardcode them in the View (even though that's a very reasonable first approach).
There are two alternatives that I know of which can reduce coupling between View and ViewModel: using an interaction service, and firing interaction requests. Both are explained very well here; you might want to take a look.
The general idea is that you abstract how asynchronous interactions are done and work with something more similar to event-based logic while at the same time allowing the ViewModel to express that it wants to interact with the user as part of an operation; the net result is that you can document this interaction and unit test it.
Edit: I should add that I have explored using Prism 4 with interaction requests in a prototype project and I was very pleased with the results (with a bit of framework code going you can even specify what's going to happen on a specific interaction request entirely in XAML!).
However, this all just seems like noise in the ViewModel to me. The prompt is more a user interface issue to guard against misclicks etc. To me this suggests it should be in the view with a confirmed prompt calling the DeleteCommand.
I agree; prompts like this should be handled in the view, as ultimately the view is what the user is seeing and interacting with, and not the view model. Once your view has obtained confirmation from the user that the DeleteCommand should be invoked, then go ahead and invoke it in your view model.
The way I see it, unit tests don't really have anything to do with user interaction, unless you're testing the view itself.
In my opinion, prompting the user consists of two parts:
The logic that determines whether or not the prompt should be shown and what should be done with the result
The code that actually shows the prompt
Part 2 clearly doesn't belong in the ViewModel.
But Part 1 does belong there.
To make this separation possible, I use a service that can be used by the ViewModel and for which I can provide an implementation specific to the environment I am in (WPF, Silverlight, WP7).
This leads to code like this:
interface IMessageBoxManager
{
MessageBoxResult ShowMessageBox(string text, string title,
MessageBoxButtons buttons);
}
class MyViewModel
{
IMessageBoxManager _messageBoxManager;
// ...
public void Close()
{
if(HasUnsavedChanges)
{
var result = _messageBoxManager.ShowMessageBox(
"Unsaved changes, save them before close?",
"Confirmation", MessageBoxButtons.YesNoCancel);
if(result == MessageBoxResult.Yes)
Save();
else if(result == MessageBoxResult.Cancel)
return; // <- Don't close window
else if(result == MessageBoxResult.No)
RevertUnsavedChanges();
}
TryClose(); // <- Infrastructure method from Caliburn Micro
}
}
This approach can easily be used not only to show a message box but also to show other windows, as explained in this answer.
I'd suggest doing this via a service which manages the modal windows. I've faced this problem quite a time ago, either. This blog post helped me a lot.
Even though it's a silverlight post, it shouldn't differ too much, compared to wpf.
I think it depends upon the prompt, but generally speaking the code logic which needs to prompt the user is often in the view model anyway, for example the user has pressed a button to delete a list item, a command is fired in the VM, logic is ran and it is apparent that this may affect another entity, the user must then choose what they wish to do, at this point you should not be able ask the View to prompt the user so I could not see anyother choice but to handle it in the VM. It is something I've always been uneasy with but I simply wrote a Confirm method in my base VM which calls a dialog service for dsiplay the prompt and returns a true or false:
/// <summary>
/// A method to ask a confirmation question.
/// </summary>
/// <param name="messageText">The text to you the user.</param>
/// <param name="showAreYouSureText">Optional Parameter which determines whether to prefix the message
/// text with "Are you sure you want to {0}?".</param>
/// <returns>True if the user selected "Yes", otherwise false.</returns>
public Boolean Confirm(String messageText, Boolean? showAreYouSureText = false)
{
String message;
if (showAreYouSureText.HasValue && showAreYouSureText.Value)
message = String.Format(Resources.AreYouSureMessage, messageText);
else
message = messageText;
return DialogService.ShowMessageBox(this, message, MessageBoxType.Question) == MessageBoxResult.Yes;
}
For me this is one of those grey crossover areas which I sometimes cannot get a firm answer for in MVVM so am interested in othere poeples approaches.
Have a look at this:
MVVM and Confirmation Dialogs
I use a similar technique in my view models because I believe that it is part of the view model to ask if it will proceed with the deletion or not and not of any visual object or view. With the described technique your model does not refer to any visual references which I don't like but to some kind of service that call a confirmation dialog or a message box or whatever else.
The way I've handled it in the past is putting an event in the ViewModel that's fired when the dialog needs to be displayed. The View hooks into the event and handles displaying the confirmation dialog, and returns the result to the caller via its EventArgs.
i think “Are you sure?” prompts belong to the viewmodel because its application logic and not pure ui stuff like animations and so on.
so the best option would be in the deletecommand execute method to call a "Are you sure" service dialog.
EDIT: ViewModel Code
IMessageBox _dialogService;//come to the viewmodel with DI
public ICommand DeleteCommand
{
get
{
return this._cmdDelete ?? (this._cmdDelete = new DelegateCommand(this.DeleteCommandExecute, this.CanDeleteCommandExecute));
}
}
put the logic in the execute method
private void DeleteCommandExecute()
{
if (!this.CanDeleteCommandExecute())
return;
var result = this.dialogService.ShowDialog("Are you sure prompt window?", YesNo);
//check result
//go on with delete when yes
}
the dialog service can be anything you want, but the application logic to check before delete is in your viewmodel.
Personally, I think it's just part of the View as there is no data
I solve this kind of problem by using the EventAggregator pattern.
You can see it explained here
Ran into this while porting an old WinForms app to WPF. I think the important thing to keep in mind is that WPF accomplishes at lot of what it does under the hood by signaling between the view model and the view with events (i.e. INotifyPropertyChanged.PropertyChanged, INotifyDataErrorInfo.ErrorsChanged, etc). My solution to the problem was to take that example and run with it. In my view model:
/// <summary>
/// Occurs before the record is deleted
/// </summary>
public event CancelEventHandler DeletingRecord;
/// <summary>
/// Occurs before record changes are discarded (i.e. by a New or Close operation)
/// </summary>
public event DiscardingChangesEvent DiscardingChanges;
The view can then listen for these events, prompt the user if need be and cancel the event if directed to do so.
Note that CancelEventHandler is defined for you by the framework. For DiscardingChanges, however, you need a tri-state result to indicate how you want to handle the operation (i.e. save changes, discard changes or cancel what you are doing). For this, I just made my own:
public delegate void DiscardingChangesEvent(object sender, DiscardingChangesEventArgs e);
public class DiscardingChangesEventArgs
{
public DiscardingChangesOperation Operation { get; set; } = DiscardingChangesOperation.Cancel;
}
public enum DiscardingChangesOperation
{
Save,
Discard,
Cancel
}
I tried to come up with a better naming convention, but this was the best I could think of.
So, to putting it in action looks something like this:
ViewModel (this is actually a base class for my CRUD-based view models):
protected virtual void New()
{
// handle case when model is dirty
if (ModelIsDirty)
{
var args = new DiscardingChangesEventArgs(); // defaults to cancel, so someone will need to handle the event to signal discard/save
DiscardingChanges?.Invoke(this, args);
switch (args.Operation)
{
case DiscardingChangesOperation.Save:
if (!SaveInternal())
return;
break;
case DiscardingChangesOperation.Cancel:
return;
}
}
// continue with New operation
}
protected virtual void Delete()
{
var args = new CancelEventArgs();
DeletingRecord?.Invoke(this, args);
if (args.Cancel)
return;
// continue delete operation
}
View:
<UserControl.DataContext>
<vm:CompanyViewModel DeletingRecord="CompanyViewModel_DeletingRecord" DiscardingChanges="CompanyViewModel_DiscardingChanges"></vm:CompanyViewModel>
</UserControl.DataContext>
View code-behind:
private void CompanyViewModel_DeletingRecord(object sender, System.ComponentModel.CancelEventArgs e)
{
App.HandleRecordDeleting(sender, e);
}
private void CompanyViewModel_DiscardingChanges(object sender, DiscardingChangesEventArgs e)
{
App.HandleDiscardingChanges(sender, e);
}
And a couple of static methods that are part of the App class that every view can use:
public static void HandleDiscardingChanges(object sender, DiscardingChangesEventArgs e)
{
switch (MessageBox.Show("Save changes?", "Save", MessageBoxButton.YesNoCancel))
{
case MessageBoxResult.Yes:
e.Operation = DiscardingChangesOperation.Save;
break;
case MessageBoxResult.No:
e.Operation = DiscardingChangesOperation.Discard;
break;
case MessageBoxResult.Cancel:
e.Operation = DiscardingChangesOperation.Cancel;
break;
default:
throw new InvalidEnumArgumentException("Invalid MessageBoxResult returned from MessageBox.Show");
}
}
public static void HandleRecordDeleting(object sender, CancelEventArgs e)
{
e.Cancel = MessageBox.Show("Delete current record?", "Delete", MessageBoxButton.YesNo) == MessageBoxResult.No;
}
Centralizing the dialog box in these static methods lets us easily swap them out for custom dialogs later.
Is there something like SESSION in Windows application? I want to store a few values to be persistent between forms.
For example: First form has some check boxes and third form process them accordingly. So I need to store the checked checkboxes somewhere.
If you're talking about different Forms within the same Application, then just create some static members on a class, it will be persisted for the lifetime of the executable.
You could only expose your CheckBoxes Checked state through properties of this form where you put your CheckBoxes on, and access these properties from your third or Process form.
public partial class MainForm : Form {
// We assume we have let's say three CheckBoxes named chkFirst, chkSecond and chkThird
public bool IsFirstChecked { get { return chkFirst.Checked; } }
public bool IsSecondChecked { get { return chkSecond.Checked; } }
public bool IsThirdChecked { get { return chkThird.Checked; } }
// Calling this form from where these checked states will be processed...
// Let's suppose we have to click a button to launch the process, for instance...
private void btnLaunchProcess(object sender, EventArgs e) {
ProcessForm f = new ProcessForm();
f.Parent = this;
if (DialogResult.OK == f.ShowDialog()) {
// Process accordingly if desired, otherwise let it blank...
}
}
}
public partial class ProcessForm : Form {
// Accessing the checked state of CheckBoxes
private void Process() {
if ((this.Parent as MainForm).FirstChecked)
// Process according to first CheckBox.Checked state.
else if ((this.Parent as MainForm).SecondChecked)
// Process according to second CheckBox.Checked state.
else if ((this.Parent as MainForm).ThirdChecked)
// Process according to third CheckBox.Checked state.
}
}
Please consider that I picked this code up the top of my head, so it might happen not to compile. Anyway, I hope that this gives you an idea of how to pass your values throughout your forms.
The biggest difference between Web and WinForm programming is that Web is stateless. SESSION and VIEWSTATE are workarounds to allow one to preserve values.
WinForms are stateful, so you don't need to go through SESSION and VIEWSTATE-like variables. A value is preserved as long as the object exists.
You can use app.config (or Settings section in Project's Properties) if you use Visual Studio, or just serialize your values and store them in some file.
If you want to persist data between independent execution of the same app (as in concurrent request serving in a HTTP farm) then just write out some XML or use a mashalling/serializing system with your runtime/plaform (dunno what it would be for C#).
Then import it again. Just watch your concurrency control.
If this is just a regular single-user windows application, create a class to model the state you want to pass around and require it in your form constructors:
internal class ApplicationState
{
// Store the selected checkbox values here, for example
public List<int> SelectedProductIds { get; }
// ... additional state ...
}
internal class EditOrderForm: Form
{
private ApplicationState applicationState;
public EditCustomerForm(ApplicationState applicationState) {
this.applicationState = applicationState;
}
// Rest of the code
}
You could use static variables instead of instances - but those are just global variables that make your code harder to read and maintain.
If you are looking to store data on a per user basis between execution sessions, you should consider Isolated Storage.
Doesn't clutter install directory
Doesn't cause issues with AnitVirus software
Part of the OS including .Net objects, don't need to install anything else
Already works with the Windows security model
Exists on a per user basis, so saved settings are separated for each user
Can serialize/deserialize obects directly into it