WPF Prism C# observe DB change and update - c#

I have a dashboard which when the constructor is called fetches data from DB and updates listbox accordingly. Once I navigate to new tab to add element to DB and navigate back to dashboard, the view is not updated. I have ObservableCollection defined, and it functions well if I for example delete the element from the list inside the dashboard view.
Since I use Prism, I have implemented INavigationAware on dashboard, and when done in that way, the update works both ways (deletion and adding new), but the problem is that the other property I need consistent just doesn't stay that way since the whole ViewModel class gets instantiated once again when I navigate back to the view.
Here is the constructor:
public DashboardViewModel()
{
service = new Service.ServiceManager();
controller = ServiceController.GetServices().FirstOrDefault(x => x.ServiceName == service.ServiceName);
lbPlugins = new ObservableCollection<Plugin>();
lbPlugins.AddRange(new DbRepository().GetContext().PluginSet);
StartStopInit();
}
It is possible to install a win service through the program, and dashboard checks if the service is in start or stop mode:
private void StartStopService()
{
if (startStopTxt == "Start")
{
service.StartService();
}
else
{
service.StopService();
}
}
The problem is that if I implement INavigationAware the service gets somehow overwritten. I was checking if it is fetching the right name, and it is...but it looks like it generates a new instance, and now I am able to start the service again...which shouldn't be possible...

I cannot see any reason for newing the service manager. Register it with unity as a singleton and have it injected as dependency:
// in the bootstrapper / module initialization
Container.RegisterType<ServiceManager>( new ContainerControllerLifetimeManager() );
// in the view model's constructor
public DashboardViewModel( ServiceManager serviceManager )
{
serviceManager.StartOrStopTheService();
}

Related

(WPF/MVVM) What's the different between an IService and ViewModel?

I wanted to use SaveFileDialog in my ViewModel, But since it's not correct that bind to a View from ViewModel, I searched for ways to do that. But I found a few answers that doesn't completely separate View form ViewModel, Like this:
public interface IOService
{
void IMessageBox(string Message);
string ISaveFileDialog(string DefaultPath);
}
public class IDialog : IOService
{
public void IMessageBox(string Message)
{
System.Windows.MessageBox.Show(Message);
}
public string ISaveFileDialog(string DefaultPath)
{
System.Windows.Forms.SaveFileDialog dg = new SaveFileDialog
{
InitialDirectory = DefaultPath,
Filter = "PDF files (*.pdf) | *.pdf"
};
dg.ShowDialog();
if (dg.FileName == null)
dg.FileName = string.Empty;
return dg.FileName;
}
}
They said that, this is a Service and using it will separate View from ViewModel. But we have make an Instance from this in ViewModel:
IDialog iDialog = new IDialog();
So I wanna know, What's the diffrence between this method and calling MessageBox or SaveFileDialog from ViewModel directly?
Note: Also I find something that said I could use a Service like the above, But implement it like this:
public class ExportViewModel : BaseViewModel
{
IOService _IOService;
public ExportViewModel(IOService ioservice)
{
_IOService = ioservice;
.
.
}
}
But I don't know how send IOService as a parameter to ExportViewModel (Because we can't create Instances from an Interface!)
You shouldn't pop up dialogs directly from your VM for automated testability.
If you call MessageBox.Show(), your test will get stuck until a person closes the dialog.
If, instead, you use "IMessageBox", for unit tests, you can inject an implementation that doesn't actually show the dialog, but rather returns a specific value (the result).
It's an abstraction used to separate UI concerns from the view model, and to allow you a means to intercept these calls within unit tests. Intercepting these calls allows you to both prevent showing a dialog during a test (which will block execution of your tests, not good) and verify the view model is acting as expected.
There is nothing special about calling it a "Service" or calling the abstraction "IService". It's just an allusion to a common pattern
I need to do something
I can't do it myself
I'll find a service I can hire to do it
I found a service that can do this for me
This service will do it for me
You could just as well call it "IDontTouchUIStuffInMuhViewModel", but that's not as elegant.
Your second problem is solved trivially at runtime by providing an implementation of the interface to your view model. Your view model shouldn't care how it's implemented (polymorphism), so you can pass in a legit implementation at runtime and a fake one during testing.
You can also accomplish this by using Dependency Injection or Inversion of Control. By using a library such as Unity to instantiate your view models, all dependencies are automatically provided, based on how you configured the container.

mvvmcross IOS: How to callback from a ViewModel to a View

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.

Managing simple state in Caliburn Micro WinRT 8.1: navigate to previously active page

I'm creating a WinRT 8.1 app based onto Caliburn.Micro (alpha2) and I'm implementing a simple state management mechanism for it. All it needs is saving a couple of name/value pairs for each of its two or three pages, and restore the current page when resumed. So I'm using the strategy summarized below; given that it seems there is no predefined mechanism for CM/WinRT, it would be interesting to get any community advice on this, or this might hopefully be useful for RT newcomers like me.
1) I define an interface (IHaveSimpleState) to be implemented by VMs having some simple state to be saved and restored. The state is represented by a dictionary where each value is a string, representing any serialized value, and the interface just has 2 methods, one for saving its state into this dictionary, and another to resume it from the dictionary. All my stateful VMs (each corresponding to a view) implement this.
2) In my app.cs (which derives from Caliburn.Application), I create a List<WeakReference<IHaveSimpleState>> to keep track of all the VMs requiring state management: in the GetInstance override which instantiates the VMs (using CM simple container) I add to this list each newly generated instance implementing IHaveSimpleState.
3) for saving state: in the app OnSuspending override, I cycle through all the VMs in this list, and invoke their SaveState method to collect data about their state in a common dictionary. Once the loop is complete, I get ApplicationData.Current.LocalSettings and I copy these data into its Values dictionary, thus effectively saving them.
4) for restoring state: in the app OnResuming override and in the OnActivated override (in the latter case, only if args.PreviousExecutionState is equal to Running, i.e. the app was not terminated by user nor crashed), I invoke a ResumeState method which cycles through all the VMs in the list and invokes their LoadState method to load the state from application data local settings.
All this seems to work fine, I only miss a point: what's the right place to restore the current "page", i.e. to tell Caliburn to navigate to the VM behind the view active at the time of suspension? I tried to do this at the end of my ResumeState method (nr.4), but it seems too early, as when I try to navigate to a VM I get an exception telling me that the corresponding view could not be found. Here is the relevant code for this method:
private void ResumeState()
{
// ... state is a dictionary wrapper class with state data
// restore state for each tracked VM
foreach (WeakReference<IHaveSimpleState> reference in _statefulViewModels)
{
IHaveSimpleState stateful;
if (reference.TryGetTarget(out stateful)) stateful.LoadState(state);
}
// move to the page which was current when the state was saved
string sType = state.Get(APP_CURRENTVM_KEY, null);
if (sType != null)
{
// not so elegant...
INavigationService navigation = IoC.Get<INavigationService>();
Type t = Type.GetType(sType);
navigation.NavigateToViewModel(t);
}
}
I tried this and seems to work, but I feel a bit unsecure about the robustness of my app given that the lifecycle handling in CM for 8.1 does not seem to be clearly documented, so I'd like to get comments or corrections from the community. First override the PrepareViewFirst method:
protected override void PrepareViewFirst(Frame rootFrame)
{
_container.RegisterNavigationService(rootFrame);
}
Then in the OnLaunched override, where before I just called DisplayRootView<MainView>(), I test the args to check whether we are resuming from suspended state, and if so I navigate to the previously active page; else I just go as before:
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
bool bResumed = false;
if (args.PreviousExecutionState == ApplicationExecutionState.Suspended)
{
AppSimpleState state = LoadState();
string sType = state.Get(APP_CURRENTVM_KEY);
if (sType != null)
{
INavigationService navigation = IoC.Get<INavigationService>();
Type t = Type.GetType(sType);
Debug.Assert(t != null);
navigation.NavigateToViewModel(t);
bResumed = true;
} //eif
}
if (!bResumed) DisplayRootView<MainView>();
}

Navigation & reinstanciate pages/viewmodel constructor

I am using Galasoft Mvvm Light toolkit, to build my application in the MVVM pattern for windows phone. I have to pages that each have their own viewmodel.
When a user starts the app he can choose new game og spin up a questions page. These to pages have each a viewmodel, and everything works using the viewmodellocator. When the user then navigates back to choose between new game and questions again. The viewmodel/page is not removed. which means when the user a second time goes into questions or new game, the constructor for the viewmodel is not called, such that the initialisation in the constructor is not run, and the view is not set correctly.
Solutions I have tried
I tried removing the backstack in the navigations, such as a new navigation to new game or questions, should spin up a new page, and thereby caling the constructor. Not working.
USing the loaded event in the view, and calling the constructor. Not working.
Tried to follow
How to reset all instances in IOC Container
But could not get it to work, might just be me.
Have anyone solve this issue, if so how should it be solved?
Code
Here you can find an example. Press questions, and press the button in there once, use backkey. and press questions again. you see that the number is now 1, this could easily be changed. But the error comes when you press the button again. Suddenly two popups are shown.
So what is the correct way to set up the viewmodel. since the view of newgame will be used when reloading an old game, just with other values, and when one wants to start a new game. Hope you understand :)
This example is only to show my problem with popups count going up for each return to the viewmodel page.
https://www.dropbox.com/s/gjbz0l8rmsxqzrd/PhoneApp8.rar
ViewModel Locator
I am in my current project using three viewmodels seen in the below code:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
namespace MVVMTestApp.ViewModel
{
public class ViewModelLocator
{
public ViewModelLocator()
{
//Holder styr på ViewModels
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//Tilføj linje her for hver ViewModel
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<MainViewModelTest>();
SimpleIoc.Default.Register<MenuViewModel>();
}
//Tilføj metode som denne for hver ViewModel
public MainViewModel Map
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public MainViewModelTest Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModelTest>();
}
}
public MenuViewModel Menu
{
get
{
return ServiceLocator.Current.GetInstance<MenuViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
I have looked into the link I reference above resetting all instances in IOC Container. But are unsure how the key works, and how to make sure the cleanup function is called when navigating away from the views. Since I would not want to clean all the viewmodels at the same time.
Navigation And viewmodelbinding
I bind my viewmodel to the view as
DataContext="{Binding Source={StaticResource Locator},Path=Map}"
I navigate back and forth using NavigationService and backbutton. From Menu to Game:
NavigationService.Navigate(new Uri("/View/MainPage.xaml", UriKind.Relative));
and in the page
protected override void OnNavigatedTo(NavigationEventArgs e)
{
//e.Content = NavigationMode.New;
//e.NavigationMode = NavigationMode(
ViewModel.MainViewModel test = new ViewModel.MainViewModel();
GC.Collect();
base.OnNavigatedTo(e);
}
and from Game to Menu:
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
//e.NavigationMode = NavigationMode.
this.DataContext = null;
GC.Collect();
base.OnNavigatedFrom(e);
//test = null;
}
And in the menu I invoke the garbage collector. As can be seen I break the MVVM structure to accommodate the problem.
Properties of your ViewModelLocator return Singletons. To make the property return a new instance each time you could simply write:
private int questCount;
public Question Quest
{
get
{
return ServiceLocator.Current.GetInstance<Question>((++questCount).ToString());
}
}
However, it will result in Question ViewModel caching. You need to release unused ViewModels by closely following the answer you linked. This results in my opinion in too much code for a simple result. There are other IOC Containers that you could use in place of SimpleIoc on Windows Phone (like ninject or unity), which may be better suited for your needs.
In a simple project (a couple-of-pages app), especially in the case of not having to much experience with IOC container, I would advise you to abandon all that SimpleIoc wiring and just call the constructor:
public Question Quest
{
get { return new Question(); }
}

Caliburn ShowDialog and MessageBox

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.

Categories

Resources