Close window through the ViewModel - c#

I have a login window that appears in the following scenarios:
Application starts: if the user has not logged in ( client id that is stored in the Properties.Setting ), the login window appears. The user logs in, the login windows closes and the main opens upon success.
The user is already logged in, but now wants to log in as a different user: the user presses a the login button and the login window appears and the user logs in. Upon success, the login window closes, and the new user's information is displayed in a small textbox.
The problem here is that I want to use the same login window in both scenarios. What I imagined was a custom callback method for each of these scenarios that would be invoked upon logon success. I'm facing the problem of accessing LogonWindow through my LogonViewModel, where the logic of logging in happens. The reason for this, is that I want to close the window upon success and do other stuff.
Here's my code:
App.xaml.cs
private void check()
{
if (isloggedin)
{
Client c = new Client();
Main m = new Main(c);
m.Show(); //
}
else
{
LoginViewModel vm = new LoginViewModel();
vm.callback = (loginViewModel, client) =>
{
// the user logged in, now show the window and close the login window
Main m = new Main(client);
m.Show();
// close LoginWindow after we're done logging in.
// But where? Who has access to window?
};
LoginWindow lw = new LoginWindow(vm);
lw.Show();
}
}
LoginViewModel.cs
public class LoginViewModel : ViewModelBase
{
public Action<LoginViewModel, Client> callback { get; set; }
public LoginViewModel()
{
}
public ICommand SignIn
{
get
{
return new RelayCommand<object>(signin, o => canLogin());
}
}
private bool canLogin()
{
return true;
}
private void signin(object o)
{
if (clogin.login())
{
Console.WriteLine("Client Found!");
Client client = clogin.getClient();
// save settings
Properties.Settings.Default.clientid = client.Uid;
Properties.Settings.Default.Save();
Client = client;
// pass the new client, and pass the current view model?
callback(this, client);
}
}
}
The initial thought was the to pass the entire window object to the viewmodel, but I think that's breaking essence of MVVM and the be able to Close the window from within my callback method since I'm already passing in the LoginViewModel. My question, is there a way to achieve what I'm trying to do? Remember, the essence is to do something after the user's logged in successfully depending from which point in my application... Thanks for the input.

In your situation I would add two events to my ViewModel
public event Action<Client> SignInSucceded;
public event Action CloseApplication;
When you create your Viewmodel you can bind the actions you want to get performed onsign in to the events.
So for example you could either create the mainwindow and show it or grab an existing one and show it.
Same procedure with the close call. Just bind an method that performs a close for all views and components that arent needed.
Because of the fact that when you bind your viewmodel to this event you 've got a valid instance of it there should be no problem on binding it. I do not know what your client class is exactly but you could move the creation of this in the method that is bind to the event and just call the event with a uid.
Edit:
In your App.xaml.cs you keep a Property that beholds the instance for your windows then you can do sthh like:
viewModel.CloseApplication += () => this.mainWindow.Close();
Just pay attention to synchronization (Dispatcher)
I hope I did understand your intentions correctly.

Related

Load popup form cross thread

I'm having trouble manipulating forms when from another thread.
I've overcome the issue by loading the form at runtime, showing it then hiding it. This means the form is created on the right thread and can be manipulated using invokes.
This is not the right way to do it. I have 3 problems that come from using this method
I can't spawn another popup box I have to use the one I created at runtime
The forms flash briefly on load - now that I have 3 forms its pretty obvious what I'm doing.
I have to use a variable bool to hold if the popup is open or not.
If anyone could point me in the right direction It would be much appreciated. Currently my code looks like:
On Main form Load:
CallerIDfrm = new frmCallerID();
CallerIDfrm.Show();
CallerIDfrm.Hide();
to manipulate the popup Im using
delegate void StringArgReturningVoidDelegate1(string CallerIDnum, string CallerIDname, string ContactID);
private void CallerID(string CallerIDnum, string CallerIDname, string ContactID)
{
if (CallerIDfrm.InvokeRequired)
{
StringArgReturningVoidDelegate1 d = new StringArgReturningVoidDelegate1(CallerID);
CallerIDfrm.Invoke(d, new object[] { CallerIDnum, CallerIDname, ContactID });
}
else
{
if (ContactID != null || ContactID != "0")
{
CallerIDfrm.ContactID = ContactID;
}
CallerIDfrm.Mainfrm = this;
CallerIDfrm.TopLevel = true;
CallerIDfrm.TopMost = true;
CallerIDfrm.lblCallerIDname.Text = CallerIDname;
CallerIDfrm.lblCallerIDnum.Text = CallerIDnum;
CallerIDfrm.Show();
CallerIDOpen = true;
}
}
To Hide the popup until required again im using:
delegate void StringArgReturningVoidDelegate2();
private void CallerIDClose()
{
if (CallerIDfrm.InvokeRequired)
{
StringArgReturningVoidDelegate2 d = new StringArgReturningVoidDelegate2(CallerIDClose);
CallerIDfrm.Invoke(d, new object[] { });
}
else
{
try
{
CallerIDfrm.Hide();
CallerIDOpen = false;
}
catch
{
}
}
}
I've tried otherways but the Popup loads as if it is not responding and I loose access to the popup.
Ultimately I'd like to be able to spawn multiple popups and have the ability to close them from the Main Form.
What I gather from your question: You have an caller api/lib/class and you like to show CallerId on a popup form when a call is received. Have a look at Events and Event Driven programming.
The following codes has not been tested, I wrote it from top of my head. Might not compile, they are here to show an example:
Create an CallReceived event in api/lib class as follows:
public event EventHandler<CallReceivedEventArgs> CallReceived;
protected void OnCallReceived(EventArgs e)
{
var handler = CallReceived;
if (handler != null)
handler(this, e);
// Note: For C# 6.0 and later, above statements can be simplified to
// CallReceived?.Invoke(this, e);
}
Note: If you don't have access to this api/lib code, create a Gateway class and put your event in there along with mechanism to trigger it.
Also create a CallReceivedEventArgs, this will be used to transfer event data:
public class CallReceivedEventArgs : EventArgs
{
public string CallerIDnum {get; set;}
public string CallerIDname {get; set;}
public string ContactID {get; set;}
}
Now, in your api/lib class raise this event whenever a call is received:
// a call received, replace dummy values with actual values
OnCallReceived(new CallReceivedEventArgs() { CallerIDnum="5554443322", CallerIDname="SOME_NAME", ContactID="SOME_CONTACT" });
Finally in your GUI form, register to said event and process accordingly.
// inside your main form class
private CallerAPI callerApi = new CallerAPI();
// somewhere inside you main form class, register to event
// from your use case, I would place it inside Main Form's constructor
callerApi.CallReceived += callerApi_Callreceived;
// receive event
void callerApi_Callreceived(object sender, CallReceivedEventArgs e)
{
var callerIDnum = e.CallerIDnum;
// etc.
// show callerId form with details
// you need to change frmCallerID's constructor accordingly
CallerIDfrm = new frmCallerID(e.CallerIDnum, CallerIDname, ContantID);
// to be able to track opened popups, you can store them inside a list
// private List<Form> openPopupList = new List<Form>();
//
// alternatively, you can assign CallerIDnum to form's name property
// and store these inside a List<string> instead of List<Form>
openPopupList.add(CallerIDfrm);
CallerIDfrm.Show();
}
Don't forget to unregister from event.
callerApi.CallReceived -= callerApi_Callreceived;
To wrap it up:
I can't spawn another popup box I have to use the one I created at runtime
You can create and show multiple frmCallerID, independent from each other.
The forms flash briefly on load - now that I have 3 forms its pretty obvious what I'm doing.
Since new approach creates CallerID forms based on events, you won't see these form flashing. It'll open whenever a CallReceived event is received.
I have to use a variable bool to hold if the popup is open or not.
A better approach would be: Register to forms FormClosed event, and remove from openPopupList accordingly.
frmCallerID.FormClosed += frmCallerID_FormClosed;
void frmCallerID_FormClosed(object sender, EventArgs e)
{
// remove form from openPopupList
frmCallerID closedPopup = (frmCallerID) sender;
openPopupList.remove(closedPopup);
}

Change Window on Button Click

In my current solution I have 2 projects:
1) A ClassLibrary project containing models and view models and
2) a project called UI containing 2 windows, a LoginWindow and a MainWindow.
Currently, the UI project has a reference to the ClassLibrary project to get the view models. The problem arises when I want to change the window from the LoginWindow to the MainWindow. I have read some articles and searches the internet for a few days now, but nothing seems to give a satisfying result in terms of what I am actually looking for. Typically it concerns a single window maintaining/switching between multiple views. That's not what I want. I want the LoginWindow to change to the MainWindow and close the LoginWindow on a button click. How is this achievable?
Edit1: I guess I need to clarify that I am using MVVM and need to access appropiate view models. I can’t just make a new instance of a certain window whenever needed and show it while hiding the other.
Edit2:
public static class MainTest
{
public static int Test()
{
Thread app = new Thread((ThreadStart)delegate
{
LoginWindow login = new LoginWindow();
LoginViewModel loginVM = new LoginViewModel();
if (loginVM.IsLoggedIn == false)
{
return -1;
}
else
{
MainWindow mainWindow = new MainWindow();
mainWindow.Show();
}
}
app.SetApartmentState(ApartmentState.STA);
app.Start();
return 0;
}
}
[SOLVED] At return -1 I get an error saying: "Anonymous function converted to a void returning delegate cannot return a value"
At app.SetApartmentState(ApartmentState.STA);I get an error saying: Argument2: cannot convert from 'void' to 'int'. I assume this has something to do with the delegate, you've written at the very top of the code.
Edit3:
Try this,
In your app.config file define the StartupUri xaml as login.xaml :
Application x:Class="CapronCRM.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Login.xaml">
And in your login xaml, when the login attemt is successfull, call this code :
MainWindow mw = new MainWindow();
mw.Show();
this.Close();
I usually use a main class to do this.
Create a main class that has this method inside
public static int Main()
{
Thread app = new Thread((ThreadStart)delegate
{
MyLoginControl login = new MyLoginControl();
MyLoginVM lvm = new MyLoginVM();
login.DataConetxt = lvm;
login.ShowDialog();
if (lvm.IsLoginFailed)
{
return;
}
else
{
MainWindow myApp = new MainWindow();
MyAppVm avm = new MyAppVm();
myApp.DataContext = avm;
myApp.ShowDialog();
}
}
app.SetApartmentState(ApartmentState.STA);
app.Start();
return 0;
}
In your project Properties (in visual studio right click on your project --> Properties) in the application tab set your Main class as startup object
This will allow you to run the login and check also the result before starting the actual application, without shutting down when the login view return
EDIT: Added an example of how you can use your VM. The point is that you can do what you want in this method. It's only purpose is to prevent the application to shutdown when the login view return (normally a Dispose() would be launched internally causing the shutdown of the application on window return)
EDIT2: there was a little error in my code. Fixed

How to pass data from one form to another form textbox in windows application?

I'm trying to pass a variable from one form to another form textbox. The 'variable' is a result of a calculation based on the user inputs.
Below is the code for the parent form(RuleInsertForm) where I'm calling the subform(Helpformula) to get the user inputs.
public partial class RuleInsertForm : Form
{
public string helpformulainputs;
}
private void RuleInsertForm_Load(object sender,EventArgs e)
{
if (helpformulainputs=="")
{
textBox_Inputs.Text = "";
}
else
{
textBox_Inputs.Text = helpformulainputs;
}
}
Below is the code for the subform(Helpformula) where i'm passing the result variable(formulainputs) to the parent form(RuleInsertForm).
public partial class HelpFormula : Form
{
public string formulainputs = string.Empty;
private void button_generateformula_Click(objectsender, EventArgs e)
{
using (RuleInsertForm insertform = new RuleInsertForm())
{
insertform.helpformulainputs = formulainputs;
this.Close();
insertform.Show();
}
}
}
Problem:
The values are getting passed to the text box but in the UI its not getting dispalyed.
so far I tried to push data back to parent form and then tried to display the data in the textbox where I failed.(I dont know where it went wrong suggest me if I can resolve the below one)
Now I need an alternative method to this for eg: instead of pushing the data back to parent form i need to make the variable available for all the forms trying to use the subform(formulainputs)
How can I acheive this process ? any suggestions are much appreciated.
The problem seems to be that insertForm.Show() does not block the execution of your button handler. Show opens the insertform as non-modal.
So after insertform is opened, the execution is continued in button_generateformula_Click and when you exit the using block, the insertform is disposed and therefore closed.
To solve this you may call insertForm.ShowDialog() instead.
For different ways of communicating between Forms look here or simply type communicate between forms into the SO search box.

Using a child window from two different parent windows in c#

My problem deals with the following 3 forms:
MainWindow.cs
SettingsWindow.cs
AuthenticationWindow.cs
Settings window contains information like "Ask for password during startup or not".
I call the Authentication Window from Settings Window in order to remove password (when the password is set).
I call the Authentication Window also during startup (when the password is set).
My Authentication Window interacts with the settings window using a Static variable(To say whether the authentication is successful or not).
But, in order to reuse the same code (that is, to call the same authentication window during startup), I am unable to tell the MainWindow whether the authentication is successful or not.
However, I must some how reuse the code.
My question is: Is it possible to notify the Child Window about whom the parent window is? If yes, Sample code please...
Hope my question is clear.
Kindly help!
I assume that Authentication Window is being used with ShowDialog() along the lines of:
AuthenticationWindow auth = new AuthenticationWindow();
if (auth.ShowDialog(this) == DialogResult.Ok)
{
// we know it was successful
}
Then within AuthenticationWindow when you've had success you'll call:
DialogResult = DialogResult.Ok;
Close();
to get the feedback above, or to signal that it failed by
DialogResult = DialogResult.Cancel;
Close();
Alternatively, you could set a property on AuthenticationWindow:
class AuthenticationWindow : Form
{
public bool Success { get; set;}
}
and set the value of Success appropriately from within the AuthenticationWindow code.
Lastly, if you want immediate feed back to be sent to your other windows, consider implementing an event:
class AuthenticationWindow : Form
{
public event Action<bool> SignalOutcome;
private OnSignalOutcome(bool result)
{
Action<bool> handler = SignalOutCome;
if (handler != null) handler(result);
}
}
Then you will have to subscribe to that event where you call the Authentication window:
AuthenticationWindow auth = new AuthenticationWindow();
auth.SignalOutcome += (outcome) => { /* do something with outcome here */ };
auth.ShowDialog(this);
ChildWindow c1=new ChildWindow();
c1.Owener=authenticationWindow;
c1.Show(); //or ShowDialog();
ChildWindow c2=new ChildWindow();
c1.Owener=anotherWindow;
c2.Show(); //or ShowDialog();
//to get the parent, use the property c.Owner
if(c.Owner is AuthenticationWindow) //AuthenticationWindow is the type of authenticationWindow instance
{
...
}

Where can I find a good tutorial on bubbling?

I'm new to C# and would like to allow to Windows forms to comminicate with each other. I googled bubbling in C# but it wasn't much help. What are some good ways I can learn bubbling?
EDIT: I want to have an options form that is shown/created when my user clicks on Edit->Preferances. I then want the settings the user changed in the options form to be relayed to the main form.
Two approaches:
Put properties on your preferences form and access them from the main form when the user clicks OK.
if (preferenceForm.ShowDialog() == DialogResult.OK)
{
this.Color = preferenceForm.UserSelectedColor;
//etc...
}
Send your preference form a delegate from the main form and let the preference form call it with the appropriate changes.
class FormSettings
{
object Color {get, set}
}
class MainForm
{
...
void ChangeSettings(FormSettings newSettings)
{ ... }
void EditPreferences_Click(...)
{
...
EditPreferencesForm editPreferences = new EditPreferencesForm(this.ChangeSettings)
editPreferences.ShowDialog();
}
}
class EditPreferencesForm
{
...
ChangeSettingsDelegate changeSettings;
FormSettings formSettings;
void OkButton_Click(...)
{
changeSettings(formSettings);
}
}
You don't state as much, but is the main form also the form that contains the Edit->Preferences menu? If so, you are already at the correct point in the code
// This is the event handler in the main form
private void mnuEditPreferencesClicked...
{
FrmPreferences frmPreferences = new FrmPreferences();
frmPreferences.ShowDialog(this);
// Preferences saved, implement changes to main form here
}
If the preferences form is not generated from the main form, fire off an event when the preferences form closes, and have the main form handle the event that way.

Categories