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.
Related
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.
I have a WPF application and i'm trying to respect the MVVM pattern rules. One of my views contains button:
<Button
Command="{Binding BrowseCommand}"
Margin="50, 0, 0, 0"
Style="{StaticResource CommonButtonStyle}"
Width="100"
Height="30">
<TextBlock
Text="Browse"/>
</Button>
Button command calls a method:
private void Browse(object sender)
{
DialogService.BrowseForDestinationPath(DestinationPath);
}
The main purpose of this method is to show "Select-directory-dialog", collect data and return it to the view model.
public static class DialogService
{
public static event Action<string> FolderBrowseRequested;
...
public static void BrowseForDestinationPath(string initialPath)
{
FolderBrowseRequested?.Invoke(initialPath);
}
}
Event defined in my DialogService class is invoked, and the subscriber method located in code-behind of the dialog fires:
protected void OnFolderBrowseRequested(string initialPath)
{
string destinationPath = initialPath;
var browsingDialog = new VistaFolderBrowserDialog();
if(browsingDialog.ShowDialog(this).GetValueOrDefault())
{
destinationPath = browsingDialog.SelectedPath;
var dataContext = DataContext as UnpackArchiveWindowViewModel;
if (dataContext != null)
dataContext.DestinationPath = destinationPath;
}
DialogService.FolderBrowseRequested -= OnFolderBrowseRequested; //so dumb
}
The problem is i really don't like this solution, I'm convinced it's unnecessarily complicated and inelegant. How to properly show a dialog on button click, collect some data and deliver it to our view model? I would like to keep View and ViewModel seperated and fully respect MVVM regime.
You could first start by describing the behavior your DialogService needs in an interface.
public interface IDialogService
{
void BrowseForDestinationPath(string initialPath);
event PathSelectedEvent PathSelected;
}
public delegate void PathSelectedEvent(string destinationPath);
Your ViewModel would contain a member of type IDialogService and subscribe to the PathSelectedEvent. The BrowseForDestinationPath method would be called using your Browse method which is called using the Command.
You could then create a user control which implements IDialogService. You could either inject this through your ViewModels constructor or if your ViewModel had a property like
public IDialogService FolderBorwser {get;set;}
the benefit of this approach is that all your view model knows about is an interface. You now delegate the responsibility of creating a concrete instance to something else. I would reccomend using an Injection Container like Unity or MEF as they handle the job of managing and resolving dependencies.
I encourage you to write your own logic because it helps you to understand the problem of opening dialogs in MVVM, but if you hit a brick wall or wan't to take the easy way out, there is a library called MVVM Dialogs that can help you with these problems.
Using this library you would write your code like this.
private void Browse(object sender)
{
var settings = new FolderBrowserDialogSettings
{
Description = "This is a description"
};
bool? success = dialogService.ShowFolderBrowserDialog(this, settings);
if (success == true)
{
// Do something with 'settings.SelectedPath'
}
}
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
I need to pass some data that I grab when the app comes back into the foreground, I have managed to trigger the method but I can't figure out how to trigger it in the existing instance of my ViewController rather than making a new instance.
Map.cs
public delegate void beginRefreshMapLine(ReturnRouteTaken returnRouteTaken);
public void updateRouteList(ReturnRouteTaken returnRouteData)
{
coordList = new List<CLLocationCoordinate2D>();
foreach(GPSData point in returnRouteData.GPSData)
{
coordList.Add(new CLLocationCoordinate2D { Latitude = double.Parse(point.Lat), Longitude = double.Parse(point.Lng) });
updateMap(this, new EventArgs());
}
}
this is the method I need to trigger in the current instance from AppDelegate.cs
AppDelegate.cs
if (GlobalVar.BoolForKey("trackMe"))
{
ReturnRouteTaken returnRouteData = webtools.GetRouteTaken(new ReturnRouteTaken() { TestDriveID = GlobalVar.IntForKey("routeTrackedID") });
if (returnRouteData.GPSData.Count > 0)
{
}
}
Here is where I am stuck, I have tried looking into delegates and invoking the method that way but I cannot get my head around how to implement it. Any help would be appreciated
I flagged this as a possible dupe, but that thread is in Obj-C, however the same concept can easily be applied using Xamarin.iOS.
Just create a Singleton class with an array or List of UIViewControllers as a property in that class and every time you instantiate a new ViewController, add it to the array orList, but also make sure you remove a view controller from the array or List when the view controller is disposed.
e.g. your singleton could look like:
public class ViewControllerHolder
{
// make constructor private to force use of Instance property
// to create and get the instance.
private ViewControllerHolder()
{
}
private static ViewControllerHolder _instance;
public static ViewControllerHolder Instance
{
get
{
if (_instance == null)
{
_instance = new ViewControllerHolder();
_instance.Controllers = new List<UIViewController>();
}
return _instance;
}
}
public List<UIViewController> Controllers { get; private set; }
}
And then you can always get access to your List of view controllers with ViewControllerHolder.Instance.Controllers and perform any add or remove operations on it.
And if you are really only interested in the one view controller, then just add that one to the List when instantiated, but do remove it when the view controller is no longer needed so you don't try to access a disposed view controller and also so that the view controller can be garbage collected when it is no longer in use.
Creating a singleton array that holds all the living UIViewControllers works, personally I like to keep things decoupled as much as I can and do not like holding and maintaining a list of objects for no real reason...
You can pass data around via:
Selector
NoticationCenter
In any UIViewController that you need to "talk" to, you can subscribe to notifications and/or register Selectors.
In your UIViewController register for which Notifications you wish to receive...
public override void ViewDidLoad()
{
base.ViewDidLoad();
NSNotificationCenter.DefaultCenter.AddObserver(this, new Selector(Const.StartRefresh), new NSString(Const.StartRefresh), null);
}
Still in your UIViewController, implement the selector that the notification center will perform a send_msg to:
[Export(Const.StartRefresh)]
void LocalStartRefresh(NSNotification notification)
{
if (notification.Name == Const.StartRefresh)
Console.WriteLine("StartRefresh from NotificationCenter:" + notification.Object);
}
In your UIApplicationDelegate, use the notification center to publish a new NSNotification to every active UIViewController that has subscribed:
public override void WillEnterForeground(UIApplication application)
{
NSNotificationCenter.DefaultCenter.PostNotificationName(Const.StartRefresh, new NSString("some custom data"));
}
Or, skip notifications and directly invoke the Selector:
In your UIViewController, implement the selector/method to call:
[Export(Const.StopRefresh)]
void LocalStopRefresh()
{
Console.WriteLine("StopRefresh from Selector");
}
In your UIApplicationDelegate, send an action to all instanced view controller instances that accept this Selector:
public override void DidEnterBackground(UIApplication application)
{
var vc = UIApplication.SharedApplication?.KeyWindow?.RootViewController;
while (vc != null)
{
if (vc.RespondsToSelector(new Selector(Const.StopRefresh)))
UIApplication.SharedApplication.SendAction(new Selector(Const.StopRefresh), vc, this, new UIEvent());
vc = vc.PresentedViewController;
}
}
I have a user control of list of Patients which I use in other Views. However when I choose one of the Patients, the selection is propagated to all the views containing an instance of the user control. How can I make each view instantiate a new instance of the user control for each view?
I am using c#
Guessing from what you stated, I'd assume that you returning a static instance of you PatientViewModel from you locator. To solve this make sure that when the property is called a new instance of the view model is generated.
Edit: Locator with different instantiation methods
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//if (ViewModelBase.IsInDesignModeStatic)
//{
// // Create design time view services and models
// SimpleIoc.Default.Register<IDataService, DesignDataService>();
//}
//else
//{
// // Create run time view services and models
// SimpleIoc.Default.Register<IDataService, DataService>();
//}
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public MainViewModel StaticMain
{
get
{
return _staticMain ?? (_staticMain = new MainViewModel());
}
}
private static MainViewModel _staticMain;
public MainViewModel NewMain
{
get
{
return new MainViewModel();
}
}
public MainViewModel NewIocMain
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>(Guid.NewGuid().ToString());
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
The Main property uses Lauent's SimpleIoc container to create an instance. One of the properties of this container is, that it treats every type as singleton. Thus if you use this VM generation method you will share the VM throughout the views.
The StaticMain property does much the same, but instead of using Laurent's container it holds a static instance of the VM which is also shared between the views.
The NewMain property creates a new VM upon every call, therefore, the VM is not shared between the views.
The NewIocMain property also creates a new VM upon every call and the VM is, therefore, not shared between the views. However, the SimpleIoc container holds a reference to the instance created. It does not release this instance automatically and you have to call SimpleIoc.Default.Unregister(key) with the key you used for creation (the Guid) to remove the instance from the container once you no longer need it.
Instead of using the SimpleIoc you obviously can opt to use another IOC Container - such as Unity for example - that allows you greater control how your instances are created and how long they live. Barring this, I'd opt for the NewMain approach given yor case.