Prism WPF Closing view and dispose autowired ViewModel - c#

In short the behaviour i want to accomplish is LOGIN->LOGOUT->LOGIN
My application starts with a login view. After authentication, it close and the MainView open:
public void Authenticated(){
MainWindow main = new MainWindow();
main.Show();
if (Application.Current.Windows.Count > 1) {
Application.Current.Windows[0].Close();
}
this.CloseAction();
}
the CloseAction is just an action variable which close from the codebehind like so
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
if ( (this.DataContext as MainWindowViewModel)!.CloseAction == null )
(this.DataContext as MainWindowViewModel)!.CloseAction = new Action(this.CloseH);
}
private void CloseH() {
this.Close();
}
}
My main cointains also two view regions pushed from the logic of the MainViewViewModel (THOSE TWO ARE USERCONTROLS)
_regionManager.RegisterViewWithRegion("FileTreeRegion", typeof(FileTree));
_regionManager.RegisterViewWithRegion("FileDetailsRegion", typeof(FileDetails));
At some point if i need to logout i run the function
public void Logout(){
Login login = new Login();
login.Show();
if (Application.Current.Windows.Count > 1) {
Application.Current.Windows[0].Close();
}
this.CloseAction();
// _eventAggregator.GetEvent<AppMessageLogout>().Publish();
// _regionManager.Regions.ToList().ForEach((r) => r.RemoveAll());
}
But when i retry to login, my views viewmodels (the usercontrols FileTree and FileDetails) are called two times (debugging the constructor method).
The previously commented lines are two attempts to solve the issue without success:
using a publish/subscribe command with a Disposable in the UserControl (I disposed only the register commands in the viewmodel since there isn't a Close() in a UserControl)
trashing all the views in the regionManager to avoid garbageCollection. Altought i guess this is useless since, as i read online, the parentWindow of the views is the MainWindow and when it close also the views do
The ViewModels are all autowired with Prism in the xaml files.

Related

WPF MVVM How to create new Window instance with no input data

In WPF (MVVM) when I create new instance of window (view) it has no entry data - but when I enter some data, close window and reopen it contains the same data as window was closed with. How to provide "fresh" window (with blank fields which need to be fulfilled) instance each time?
I've tried many things and right now my class "ViewService" looks like this.
public class ViewService : IViewService
{
public void Show<T>()
{
try
{
T window = Activator.CreateInstance<T>();
var view = window as Window;
view.Show();
}
catch (Exception)
{
}
}
public void ShowDialog<T>()
{
try
{
T window = Activator.CreateInstance<T>();
var view = window as Window;
view.ShowDialog();
}
catch (Exception)
{
}
}
Thank you very much for your help.
PS. I use SimpleIoC container to register viewmodels.
Simpleioc will give you a singleton for any type and hence the same instance of a given viewmodel each time.
Hence, your problem.
Either.
Use a different dependency injection system which is more sophisticated and will return a new instance each time.
Or.
Don't inject your window viewmodels at all.

How to pass data from Window to MainWindow in WPF?

Im newbie to programming and now i have a problem with my new login window.
I created a new window for login to my server database,but in my mainwindow how do i call it? I don't know how to draw it up better.
In my login window:
using MyS_Database;
MyServer_Database server;
public void LoginButton_Click(object sender, RoutedEventArgs e)
{
server = new MyServer_Database("ClientName","ServerIP","","",UserID.Text,UserPassword.Password.ToString(),"01",MyServer_Database.LoginType.User,out serverresult)
swith(serverresult)
{
case 0:
Mainwindow.Show();
this.Close();
break;
default:
Environment.Exit(0);
}
}
But when I call a method on my MainWindow which is communicating with the server i have to call it like:
server.GetDataFromUserList();
But It doesn't recognize server,I tried something like Win1.server or similars but I failed.
How could i pass it? Thank you in advance!
The quick answer is to make server public like this:
public MyServer_Database Server { get; set; }
Then you can call it in MainWindow with:
Win1.Server.GetDataFromUserList();
But this is not a good approach. Better approach is to abstract away these kind of operations in different classes via MVVM. Read more about MVVM and you'll find great approaches.
Edit
You should also do one one of these two things:
Don't close Win1 instead do this.Hide();
Keep the instance of MyServer_Database in MainWindow by passing it to the constructor of Win1.
You should put this in MainWindow
public MyServer_Database Server { get; set; }
and When needed call this:
var Win1 = new LoginWindow(Server);
Then you have access to Server object in MainWindow

ViewModels (and maybe Views) still active after switching Views with RequestNavigate in WPF/Prism

Most of my view models subscribe to a common event using Prism's EventAggregator on a WPF project. Basically, a vocal command triggers this event on a view, and as a response the view will publish another event containing its specific message to a text-to-speech module.
However, when I implemented this, I realized that when using RegionManager's RequestNavigate to switch to another view, the previous view model is still somehow active. When I trigger the common event for the most recent view, it is also triggered for the previous view.
Simplified example :
Start at View 1
Trigger common event
Response : message from View 1
RequestNavigate to View 2
Trigger common event
Response : message from View 2, then message from View 1
RequestNavigate to View 3
Trigger common event
Response : message from View 3, then View 2, then View 1
etc.
I placed a breakpoint on View 1, View 2 and View 3's common event, and each time I get a message from a view, its breakpoint is hit.
What I would like is simple : I don't want the previous ViewModel (and possibly View too) to be still somehow active when I'm switching Views. Even better would be for them to be garbage collected, because I also had some weird cases where by navigating to View 1, View 2 and View 1 again, the message for View 1 was sent twice (and its breakpoint also hit twice), so I'm not even sure if multiple references for the ViewModels are created, which could potentially lead to a memory leak.
I tried to reproduce this behavior by creating another project with just the essentials, so here's the code. I'm using Visual Studio 2017 with .net framework 4.5.2 and Ninject.
Shell.xaml
<Window x:Class="PrismTest.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prsm="http://prismlibrary.com/"
mc:Ignorable="d">
<Grid>
<ContentControl Name="MainRegion" prsm:RegionManager.RegionName="MainRegion" />
</Grid>
</Window>
NinjectPrismBootstrapper.cs
public class NinjectPrismBootstrapper : NinjectBootstrapper
{
protected override void InitializeModules()
{
base.InitializeModules();
// Text to speech
Kernel.Bind<SpeechSynthesizer>().ToSelf().InSingletonScope();
Kernel.Bind<INarrator>().To<StandardNarrator>().InSingletonScope();
Kernel.Bind<INarratorEventManager>().To<NarratorEventManager>().InSingletonScope();
// View models
Kernel.Bind<MainPageViewModel>().ToSelf();
Kernel.Bind<SecondPageViewModel>().ToSelf();
// Views
Kernel.Bind<object>().To<MainPageView>().InTransientScope().Named(typeof(MainPageView).Name);
Kernel.Bind<object>().To<SecondPageView>().InTransientScope().Named(typeof(SecondPageView).Name);
Kernel.Bind<Shell>().ToSelf();
var narratorEventManager = Kernel.Get<INarratorEventManager>();
var regionManager = Kernel.Get<IRegionManager>();
regionManager.RegisterViewWithRegion("MainRegion", typeof(MainPageView));
}
protected override DependencyObject CreateShell()
{
return (Shell)Kernel.GetService(typeof(Shell));
}
protected override void InitializeShell()
{
base.InitializeShell();
Application.Current.MainWindow = (Shell)this.Shell;
Application.Current.MainWindow.Show();
}
}
MainPageView.xaml (my starting page)
<UserControl x:Class="PrismTest.Views.MainPageView"
namespaces...>
<StackPanel>
<TextBlock Text="Main page"/>
<Button Content="Narrator speaks" Command="{Binding Path=NarratorSpeaksCommand}" />
<Button Content="Next page" Command="{Binding Path=GoToNextPageCommand}"/>
</StackPanel>
</UserControl>
MainPageView.xaml.cs
public partial class MainPageView : UserControl
{
public MainPageView(MainPageViewModel dataContext)
{
InitializeComponent();
this.DataContext = dataContext;
}
}
MainPageViewModel (View model for MainPageView)
public class MainPageViewModel : BindableBase, IRegionMemberLifetime, INavigationAware
{
private readonly IEventAggregator _eventAggregator;
private readonly IRegionManager _regionManager;
public DelegateCommand GoToNextPageCommand { get; private set; }
public DelegateCommand NarratorSpeaksCommand { get; private set; }
public MainPageViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
{
_eventAggregator = eventAggregator;
_regionManager = regionManager;
ConfigureCommands();
//The original common event triggered by a vocal command is simulated in this project by simply clicking on a button
_eventAggregator.GetEvent<CommonEventToAllViews>().Subscribe(NarratorSpeaks);
}
private void ConfigureCommands()
{
GoToNextPageCommand = new DelegateCommand(GoToNextPage);
NarratorSpeaksCommand = new DelegateCommand(ClickPressed);
}
private void GoToNextPage()
{
_regionManager.RequestNavigate("MainRegion", new Uri("SecondPageView", UriKind.Relative));
}
private void ClickPressed()
{
_eventAggregator.GetEvent<CommonEventToAllViews>().Publish();
}
private void NarratorSpeaks()
{
_eventAggregator.GetEvent<NarratorSpeaksEvent>().Publish("Main page");
}
}
I don't need to put the code for SecondPageViewModel and SecondPageView, because it's the exact same code except RequestNavigate sends the user back to MainPageView and its NarratorSpeaks method sends a different string.
What I tried :
1) Making MainPageViewModel and SecondPageViewModel inherit IRegionMemberLifetime and setting KeepAlive to false
2) Inheriting INavigationAware and returning false in IsNavigationTarget method
3) Adding this to OnNavigatedFrom method from INavigationAware :
public void OnNavigatedFrom(NavigationContext navigationContext)
{
var region = _regionManager.Regions["MainRegion"];
var view = region.Views.Single(v => v.GetType().Name == "MainPageView");
region.Deactivate(view);
}
Worth noting : even without the deactivate part, if I put a breakpoint after var region = _regionManager.Regions["MainRegion"]; and check region.views, there is only one result, no matter how much I switch views.
Nothing worked, events keep being triggered in previous views as I switch views back and forth.
So, I'm kind at a loss here. I'm not sure if it's my way of registering Views and ViewModels in Ninject that triggers this, or something else, but if someone has a suggestion, I'll gladly take it.
Thanks!
I had similar problems in the past. Have you considered unsubscribe from events when navigated from?

How to access UserControl's DataContext from another UserControl?

I'm working on a legacy application that's written using Silverlight 5, The application contains lot's of anti-patterns and bad practices. I'm responsible for adding real-time interactions (such as notification) using SingalR.
By the way, They're using these WCF RIA Services for interacting with authentication.
They have a Main page, this page is the place where I'm getting user's notification and show them for logged in users:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
//...
}
}
So as you can see I didn't set DataContext property as long as user is logged in, I need to set MainPage's DataContext after a user logs in to application, So I have to do that in LoginOperation_Completed inside LoginForm page:
public partial class LoginForm : StackPanel
{
private LoginRegistrationWindow parentWindow;
private LoginInfo loginInfo = new LoginInfo();
public LoginForm()
{
InitializeComponent();
//...
}
private void LoginOperation_Completed(LoginOperation loginOperation)
{
if (loginOperation.LoginSuccess)
{
// Here I need to access MainPages's DataContext property and set it with my ViewModel
}
}
}
Now my question is that, how can I set MainPage's DataContext property inside another class (in this case LoginFrom)?
I have also tried to give an ID to my MainPage user control and access it like this:
mainPage.DataContext = new NotificationItemViewModel();
But the compiler gives me this error:
The name 'mainPage' does not exist in the current context
I finally figured out How to solve my question, There is a simple way to achieve this, I should have created the static instance of the MainPage class in the class itself:
public partial class MainPage : UserControl
{
public static MainPage Instance { get; private set; }
public MainPage()
{
InitializeComponent();
Instance = this;
}
}
Now I can access to the MainPage's DataContext this way:
MainPage.Instance.DataContext = new NotificationItemViewModel();
You need to name your MainPage UserControl where you pasted it in LoginForm XAML. Not in the definition of MainPage.

Swapping MainWindows in WPF?

I have two "MainWIndows". A Login screen and the actual Content Main Window. This is the process in which i want to happen.
User starts application
Clicks Login Button on the Login Window
Initialize the Content Window
Wait until all my lists and Data have been gathered, parse, and added to ListViews
Close the Login Window and show the Main Window. (Make MainWindow the main Window)
I am having issues actually hiding the main window, but i still need to be able to initialize it so i can gather all my data.
I added this to my App.xaml:
<Application.MainWindow>
<NavigationWindow Source="MainWindow.xaml" Visibility="Hidden"></NavigationWindow>
</Application.MainWindow>
Here is my some of my LoginWindow code:
// Login complete, load the MainWindow Data
MainWindow mainWindow = new MainWindow();
mainWindow.setLoginWindow = this;
mainWindow.InitializeComponent(); //mainWindow.Show();
And the code i am using in the MainWindow:
public partial class MainWindow : MetroWindow
{
Window LoginWindow;
public Window setLoginWindow { get { return LoginWindow; } set { LoginWindow = value; } }
public MainWindow()
{
InitializeComponent();
// Hide the window to load Data, then on completion, close LoginWindow and show MainWindow: ::: LoginWindow.Close();
LoadData();
}
public void LoadData()
{
// Add player's to list ....
// Done loading data, show the window
LoginWindow.Close();
this.Visibility = Visibility.Visible;
}
}
The Question
How would i do this properly? Also, i want to keep the Focus on the LoginWindow until the MainWIndow has been shown.
(off the top of my head so watch for syntax errors etc...)
Edit the App.xaml and do this:
Startup="StartUp"
Then edit the App.xaml.cs and add a StartUp event like so:
private void StartUp(object sender, StartupEventArgs args)
{
...
}
Then inside you can call your login window and then start your main window after that.
var login = new LoginWindow();
if(login.ShowDialog()!=true)
{
//login failed go away
return
}
var mainWin = new MainWindow();
mainWin.Show();
I think the problem is that you are putting you logic in the MainWindow. Try putting it in the static Main() method or in the class App: Application class.
Here is a code project where he is doing something similar for a splash screen:
http://www.codeproject.com/Articles/38291/Implement-Splash-Screen-with-WPF
Here is a tutorial for working with the App.xaml.cs
http://www.wpf-tutorial.com/wpf-application/working-with-app-xaml/

Categories