WPF & MVVM: CloseAction don't work - c#

I write Application with WPF, MVVM, Prism and Unity. From of one Window I start secondary Window:
public void ShowForm(IPrescriptionViewModel viewModel)
{
var view = new PrescriptionForm();
view.SetDataContext(viewModel);
view.ShowDialog();
}
Method SetDataContext
public void SetDataContext(IPrescriptionViewModel viewModel)
{
if (viewModel == null) return;
DataContext = viewModel;
if (viewModel.CloseAction == null)
viewModel.CloseAction = new Action(this.Close);
}
In BTMPrescriptionViewModel is a Property
public Action CloseAction { get; set; }
and CloseCommandExecute
public ICommand CloseCommand => new RelayCommand(CloseCommandExecute);
private void CloseCommandExecute()
{
CloseAction();
}
It works fine, but only once - the first. After closing the secondary window and again open it, it is no longer closed with command button, only with close button of the window. After closing and opening of the parent window, secondary window can I again close with command button, but again only once.

Lacking a good Minimal, Complete, and Verifiable code example that reliably reproduces the problem, it's impossible to say for sure what the issue is. But, based on the code you posted here, it appears that you are creating a new window each time, but only setting the CloseAction property once.
Because the CloseAction delegate value you assign captures this to call the Close() method, it is always calling Close() on the first window you create, not any of the ones you create afterward.
Without a more complete code example, it's not clear what the best way for you to accomplish your goal is. But the basic issue would probably be solved if you just took out the null check and always assigned the value:
public void SetDataContext(IPrescriptionViewModel viewModel)
{
if (viewModel == null) return;
DataContext = viewModel;
viewModel.CloseAction = this.Close;
}
Note that you also don't need to explicitly call the delegate constructor. The compiler has inference rules for dealing with delegate types, and simply referring to the method name is sufficient.

Related

Hide an Instance of a Window

how do I hide an Instance of a Window which was created in an Method, in another Method?
I have a Login Window with a button "Register" and when you click that, the Register-Window is opening, and the Login-Window is hiding (That is working fine).
The Problem now is, i have on the Register-Window a Button "Back" which should hide the Register Window and Show the Login Window.
I Can show the Login Window with : "Application.Current.MainWindow.Show();", but i dont know how to hide the Register-Window. Normally i would just go for "Regis.Hide()", but i can´t do that because i have the instance of Regis in the Method, which opens the Register Window.
I understand, that if i create a second instance in the second Method and hide this, that that wont work because there are 2 seperate instances now.
But as i said, i dont know how i should do this either.
OpenRegistrationGui:
ICommand _RegisterBack;
public void Open()
{
Application.Current.MainWindow.Hide();
Register regis = new Register();
regis.Show();
}
public ICommand RegisBackCommand
{
get
{
if (_RegisterBack == null)
{
_RegisterBack = new RelayCommand(
param => Back()
);
}
return _RegisterBack;
}
}
public void Back()
{
Application.Current.MainWindow.Show();
//I Want to Hide the Regis here, but i cant use the Instance from above.
}
MyViewModel:
OpenRegistrierungsGUI RegisGUI = new OpenRegistrierungsGUI();
public ICommand RegisBack
{
get => RegisGUI.RegisBackCommand;
set => RegisBack = RegisGUI.RegisBackCommand;
}
The Same Thing is with the Open() Method.
The Result i want is simply that the Register Window is hiding and the Login Window shows off.
This will resolve your issue by making regis a member variable that the whole class has access to...
private Register regis;
public void Open()
{
Application.Current.MainWindow.Hide();
// Create the register window, if it doesn't exist
if (regis == null)
{
regis = new Register();
}
regis.Show();
}
public void Back()
{
// hide the register window, if it exists
if (regis != null)
{
regis.Hide();
}
Application.Current.MainWindow.Show();
}
There are different ways of doing this, but this one ensures only one instance of the register window and gives you the show/hide ability you need.
simply use regis.hide(); hopefully its work for you.
private Register regis;
public void Back()
{
Application.Current.MainWindow.Show();
regis.hide();
}
here you want to use one function variable into another
Declare regis globally than initialize it in method where you first you use it .
In this way there will be only one instance of regis .
Now you can do
regis.Hide()
or
regis.Show()
in any method of your class
I found the solution:
I fired the Open() Method (Which worked perfectly).
When I wanted to fire the Back() Method I accidentally created an Instance of "OpenRegistrationGui" and tried to Close it, which it obviousely can´t, because its another Instance.
I made the Property "RegisBackCommand" Static and fired it now directly from the ViewModel, which works now.
Anyway... Thank you all for your fast Answers and help!!!

RelayCommand from lambda with constructor parameters

If, in a XAML file, I bind a Button to "Command" from the following class, then clicking the Button does not cause DoIt to be executed:
class Thing()
{
public Thing(Foo p1)
{
Command = new RelayCommand(() => DoIt(p1));
}
private DoIt(Foo p)
{
p.DoSomething();
}
public ICommand Command { get; private set; }
}
However, it does work if I initialize a field from p1 and pass the field as a parameter to the method call inside the lambda:
class Thing()
{
private Foo field;
public Thing(Foo p1)
{
field = p1;
Command = new RelayCommand(() => DoIt(field));
}
private DoIt(Foo p)
{
p.DoSomething();
}
public ICommand Command { get; private set; }
}
Why does the former fail, but the latter work as expected?
Probably relevant: How do closures work behind the scenes? (C#)
EDIT: To clarify, the following would also work for me. However, I would still like to know why the second example did what I expected, but the first one did not.
class Thing()
{
private Foo field;
public Thing(Foo p1)
{
field = p1;
Command = new RelayCommand(DoIt);
//Command = new RelayCommand(() => DoIt()); Equivalent?
}
private DoIt()
{
field.DoSomething();
}
public ICommand Command { get; private set; }
}
It's an old question but I recently stumbled upon this topic and it's worth answering.
The reason for this strange behavior originates from the MVVM Light implementation of RelayCommand. The execute and canexecute handlers are stored as WeakAction _execute and WeakFunc<bool> _canExecute in the relay command. The WeakAction is an attempt to allow the GC cleanup of viewmodels when the command is still referenced by the UI for some reason.
Skipping some details, the bottom line is: assigning a viewmodel method as handler works great, because the WeakAction will stay alive as long as the viewmodel stays alive. For a dynamically created Action, the situation is different. If the only reference to that action is inside the RelayCommand, only a weak reference exists and GC can collect the action at any time, turning the whole RelayCommand into a dead brick.
Ok, time for the details. The implementation of WeakAction is not blindly storing a weak reference to the action - this would lead to many disappearing references. Instead, a combination of a weak Delegate.Target reference and an Delegate.MethodInfo is stored. For a static method, the method will be stored by strong reference.
Now, this leads to three categories of lambda:
static method: () => I_dont_access_anything_nonstatic() will be stored as a strong reference
closure on member variables: () => DoIt(field) the closure method will be created in the viewmodel class, the action target is the viewmodel and will stay alive as long as the viewmodel stays alive.
closure on local variables: () => DoIt(p1) the closure will create a separate class instance to store the captured variables. This separate instance will be the action target and there won't be any strong reference to it - GC cleans up at some point
Important: as far as I can tell, this behavior might change with Roslyn: Delegate caching behavior changes in Roslyn so there is a chance that todays working code with case (2) turns into non-working code with Roslyn. However, I didn't test this assumption, it might work out completely different.
your Problem is that calling the Method DoIt is inside another anonymous Method created by the lamda expression. Your expression
() => DoIt(p1);
creates a anonymous Method without parameters (seen as there are no variables provided in the first braces).
I would recommend you to use the generic constructor from mvvm-light for creating the Command:
class Thing
{
public Thing()
{
Command = new GalaSoft.MvvmLight.Command.RelayCommand<bool>(DoIt);
}
private void DoIt(bool p)
{
p.DoSomething(p);
}
public System.Windows.Input.ICommand Command { get; private set; }
}
Then just bind the Button to the "Command".

Windows Forms, Designer, and Singleton

I'm trying to work with Windows Forms and User Controls and thus far it's been nothing but a headache. I can't make the form or the controls static because the designer doesn't like that and when I use Singleton on my form and controls, the designer still throws errors at me.
My FormMain:
public partial class FormMain : Form
{
private static FormMain inst;
public static FormMain Instance
{
get
{
if (inst == null || inst.IsDisposed)
inst = new FormMain();
return inst;
}
}
private FormMain()
{
inst = this;
InitializeComponent();
}
MainScreen.cs:
public partial class MainScreen : UserControl
{
private static MainScreen inst;
public static MainScreen Instance
{
get
{
if (inst == null || inst.IsDisposed)
inst = new MainScreen();
return inst;
}
}
private MainScreen()
{
inst = this;
InitializeComponent();
}
If the constructor of MainScreen is public the program runs, but when I change it to private I now get an error in FormMain.Designer.cs saying "'Adventurers_of_Wintercrest.UserControls.MainScreen.MainScreen()' is inaccessible due to its protection level". It points to this line:
this.controlMainScreen = new Adventurers_of_Wintercrest.UserControls.MainScreen();
I think this is the instance of the class that the designer makes by default. Should I ditch the designer? Or is there a way around this? Or is there another way to make class properties accessible without using Singleton (since I can't seem to make the form or controls static)? Any help would be greatly appreciated.
You need to keep a reference to each instance of each form if you want to access the public properties of the instantiated form.
One way is to have a class with a static variable for each type of form:
class FormReferenceHolder
{
public static Form1 form1;
public static Form2 form2;
}
This way you would set the static variable whenever you instantiate a form, and then you can access that variable from anywhere in the program. You can go one step further with this and use properties that set up the form if it doesn't already exist:
class FormReferenceHolder
{
private static Form1 form1;
public static Form1 Form1
{
get
{
if (form1 == null) form1 = new Form1();
return form1 ;
}
}
}
...
static void Main()
{
Application.Run(FormReferenceHolder.Form1 );
}
I think I answered a previous question about this, which looks like it is what got you started down this route. The first point is that I wasn't recommending this pattern specifically, just trying to teach you more about how software developers can manage scope.
That said, the problem you are facing isn't insurmountable. You could hobble a public constructor by throwing an exception at runtime and not at design time, for instance, and modify Program.cs to use the static Instance instead of manually constructing the form.
But.
As I said in the other question, the better option would be to change architecture so that you don't need your library code to directly manipulate the GUI in the first place.
You can do this either by just having the GUI ask the library questions when it thinks it needs new data (simple functions) or by letting the GUI be notified when something needs to change. Either method would be better than having the library fiddle with labels directly.
A good place to start would be something like an MVC (model-view-controller) architecture, which I was alluding to in my previous answer. It might be best, though, to give us an idea of what your high-level program structure looks like now on a bit more detail. What are the main classes you are using in your system (not just the ones you've mentioned so far)? What is the main responsibility of each, and where does each live? Then our recommendations could be a little more specific.
EDIT
So, I have mocked up a quick demo of a possible alternative architecture, based on your comment.
I have the following in my project:
FormMain (Form)
TitleScreen (UserControl)
InGameMenu (UserControl)
MainScreen (UserControl)
GameController (Class)
GameModel (Class)
I didn't use Date and LoadSave, for now.
FormMain simply has an instance of each UserControl dropped on it. No special code.
GameController is a singleton (since you tried to use this pattern already and I think it would be helpful for you to try using a working version of it) that responds to user input by manipulating the model. Note well: you don't manipulate the model directly from your GUI (which is the View part of model-view-controller). It exposes an instance of GameModel and has a bunch of methods that let you perform game actions like loading/saving, ending a turn, etc.
GameModel is where all your game state is stored. In this case, that's just a Date and a turn counter (as if this were going to be a turn-based game). The date is a string (in my game world, dates are presented in the format "Eschaton 23, 3834.4"), and each turn is a day.
TitleScreen and InGameMenu each just have one button, for clarity. In theory (not implementation), TitleScreen lets you start a new game and InGameMenu lets you load an existing one.
So with the introductions out of the way, here's the code.
GameModel:
public class GameModel
{
string displayDate = "Eschaton 23, 3834.4 (default value for illustration, never actually used)";
public GameModel()
{
// Initialize to 0 and then increment immediately. This is a hack to start on turn 1 and to have the game
// date be initialized to day 1.
incrementableDayNumber = 0;
IncrementDate();
}
public void PretendToLoadAGame(string gameDate)
{
DisplayDate = gameDate;
incrementableDayNumber = 1;
}
public string DisplayDate
{
get { return displayDate; }
set
{
// set the internal value
displayDate = value;
// notify the View of the change in Date
if (DateChanged != null)
DateChanged(this, EventArgs.Empty);
}
}
public event EventHandler DateChanged;
// use similar techniques to handle other properties, like
int incrementableDayNumber;
public void IncrementDate()
{
incrementableDayNumber++;
DisplayDate = "Eschaton " + incrementableDayNumber + ", 9994.9 (from turn end)";
}
}
Things to note: your model has an event (in this case, just one of type EventHandler; you could create more expressive types of events later, but let's start simple) called DateChanged. This will be fired whenever DisplayDate changes. You can see how that happens when you look at the property definition: the set accessor (which you will NOT call from your GUI) raises the event if anyone is listening. There are also internal fields to store game state and methods which GameController (not your GUI) will call as required.
GameController looks like this:
public class GameController
{
private static GameController instance;
public static GameController Instance
{
get
{
if (instance == null)
instance = new GameController();
return instance;
}
}
private GameController()
{
Model = new GameModel();
}
public void LoadSavedGame(string file)
{
// set all the state as saved from file. Since this could involve initialization
// code that could be shared with LoadNewGame, for instance, you could move this logic
// to a method on the model. Lots of options, as usual in software development.
Model.PretendToLoadAGame("Eschaton 93, 9776.9 (Debug: LoadSavedGame)");
}
public void LoadNewGame()
{
Model.PretendToLoadAGame("Eschaton 12, 9772.3 (Debug: LoadNewGame)");
}
public void SaveGame()
{
// to do
}
// Increment the date
public void EndTurn()
{
Model.IncrementDate();
}
public GameModel Model
{
get;
private set;
}
}
At the top you see the singleton implementation. Then comes the constructor, which makes sure there's always a model around, and methods to load and save games. (In this case I don't change the instance of GameModel even when a new game is loaded. The reason is that GameModel has events and I don't want listeners to have to unwire and rewire them in this simple sample code. You can decide how you want to approach this on your own.) Notice that these methods basically implement all the high-level actions your GUI might need to perform on the game state: load or save a game, end a turn, etc.
Now the rest is easy.
TitleScreen:
public partial class TitleScreen : UserControl
{
public TitleScreen()
{
InitializeComponent();
}
private void btnLoadNew(object sender, EventArgs e)
{
GameController.Instance.LoadNewGame();
}
}
InGameMenu:
public partial class InGameMenu : UserControl
{
public InGameMenu()
{
InitializeComponent();
}
private void btnLoadSaved_Click(object sender, EventArgs e)
{
GameController.Instance.LoadSavedGame("test");
}
}
Notice how these two do nothing but call methods on the Controller. Easy.
public partial class MainScreen : UserControl
{
public MainScreen()
{
InitializeComponent();
GameController.Instance.Model.DateChanged += Model_DateChanged;
lblDate.Text = GameController.Instance.Model.DisplayDate;
}
void Model_DateChanged(object sender, EventArgs e)
{
lblDate.Text = GameController.Instance.Model.DisplayDate;
}
void Instance_CurrentGameChanged(object sender, EventArgs e)
{
throw new NotImplementedException();
}
private void btnEndTurn_Click(object sender, EventArgs e)
{
GameController.Instance.EndTurn();
}
}
This is a little more involved, but not very. The key is, it wires up the DateChanged event on the model. This way it can be notified when the date is incremented. I also implemented another game function (end turn) in a button here.
If you duplicate this and run it, you'll find that the game date is manipulated from lots of places, and the label is always updated properly. Best of all, your controller and model don't actually know anything at all about the View-- not even that it's based on WinForms. You could as easily use those two classes in a Windows Phone or Mono context as anything else.
Does this clarify some of the architecture principles I and others have been trying to explain?
In essence the problem is that when the application runs, it's going to try to instantiate the main form-window. But by using the Singleton pattern, you're essentially forbidding the application from doing that.
Take a look at the sample code here:
http://msdn.microsoft.com/en-us/library/system.windows.forms.application.aspx
You'll notice in particular this section:
[STAThread]
public static void Main()
{
// Start the application.
Application.Run(new Form1());
}
Notice how the program is trying to instantiate Form1. Your code says, nah, I don't really want that since you mark the constructor as private (same holds true for static forms as well). But that's counter to how windows forms is supposed to work. If you want a singleton form-window, just don't make any more. Simple as that.

Unable to cast object of type

I get an error trying to send a string from one window to another in my wpf application:
Unable to cast object of type 'WpfApplication4.LoginWindow' to type 'WpfApplication4.MainWindow'.
In my login window the error is on this line:
((MainWindow)Application.Current.MainWindow).StudentID = UserAuthenticationID;
In my main window I have this to test:
public string StudentId { get; set; }
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
label1.Content = StudentID;
}
What am I doing wrong?
EDIT:
No answer has made sense so far, which will be due to my obscure question, I created a wpf application and I have two windows MainWindow.xaml and LoginWindow.xaml.
I want to pass a string (student id) from the login window to the main window after authentication.
I thought the above method was how to do it, as I read it: ((MainWindow)Application.Current.MainWindow).StudentID says where I am intending the string UserAuthenticationID to be sent to?
Then in the MainWindow.xaml I get the string UserAuthenticationID and set it, I then assign a labels content to this public string?
Presumably you create your login window from your main window. If you need to reference your main window from your login window then pass a main window reference to your login window when you construct it. Eg
void MainWindow_Loaded(object sender, RoutedEventArgs e) {
LoginWindow login = new LoginWindow(this);
login.ShowDialog();
}
class LoginWindow : Window {
MainWindow app_window;
public LoginWindow(MainWindow app_window) {
Owner = app_window;
this->app_window = app_window;
}
}
Error coming because its trying to convert Application.Current.MainWindow to MainWindow, which is infact LoginWindow.
suggestion when you are coverting one object other made check before it
if(Application.Current.MainWindow is MainWindow)
{
/// than do the code
}
It looks like your trying to feed the UserAuthenticationID from your login dialog back to your main window where you store it as StudentId.
Why not handle that back in the main window Eg:
if (login.ShowDialog())
StudentId = login.UserAuthenticationID;
It seems that your Application.Current.MainWindow is of Type LoginWindow. But you want to cast it to MainWindow. Both derive (maybe) from Window. But you can't cast a derived class to another derived class.
//Edit
try
((LoginWindow)Application.Current.MainWindow)
or change your Application.Current.MainWindow to a object of type MainWindow
//Edit 2
I think i understood what your intention was. You could try the following:
In your LoginWindow where you press the Button "Login" handle the Buttonevent (Click), get the ID whereever it comes (TextBox) from, put it in a new instance of MainWindow and set the
Application.Current.MainWindow
to the new instance of MainWindow. If I'm wrong on how you will do it, you should give more facts.
Other solution is the LoginDialog solution from Ricibob.
You need to restructure the design of your program. What you want to do isn't terribly hard; you're essentially asking for a good way to communicate between forms.
First off, I suggest not using Application.Current.MainWindow unless you're really sure you need to. You can get apps to work using it, but it's not good design and leads to problems.
We'll start off with the definition of some OtherWindow that will be the popup.
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for OtherWindow.xaml
/// </summary>
public partial class OtherWindow : Window
{
public OtherWindow()
{
InitializeComponent();
}
public string SomeData
{
get
{
//you'll probably want to return the value of a textbox or something else
//the user fills in.
return "Hello World!";
}
}
}
}
From a communication point of view, all that's really important is that it has a property (or properties) with the information that the main form will need to access. Obviously I've omitted all of the code for actually collecting the data to return and instead hard code a value for this example.
Now for the mainform. (All I've added is a button and a label.)
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
OtherWindow other = new OtherWindow();
other.Closed += (sender2, e2) =>
{
label1.Content = other.SomeData;
};
//either of the methods below, depending on desired behavior.
other.Show();
//other.ShowDialog();
}
}
}
While there are quite a lot of ways of making different windows/forms communicate, this is my favorite. All of the logic for the interaction between the windows is in once place (the button click event). The main form clearly "owns" OtherWindow, and OtheWindow doesn't need to know a thing about MainWindow, it only need to know how to collect some information. MainWindow will take care of pulling out the information that it needs.
The code for the button click even reads just like what you want it to do: create a new window, when it's closed, set its content to my label, and show the form. It's all in once place, and in the order that I logically think through it (you can attach the event handler after Show if you want).
Oh, and this is exactly the same way that I would do this in a winforms app too, in case anyone cares, barring the fact that some of the class/property names will be different.

Calling method from another window (Class) issue

In code behind file of the main window of WPF application I have a method quering a database with LINQ to SQL and writing results to an ObservableCollection:
public void GetStateByDate(string shcode)
{
MydbDataContext contextSts = new MydbDataContext();
_ShAvaQuCollection.Clear();
var sts = from p in contextSts.SAties where p.ShID == shcode select p;
foreach (var p in sts)
_ShAvaQuCollection.Add(new ShAvaQu
{
ShCode = p.ShID,
SiID = p.SiID,
PrCat = p.PrCat
});
}
When I call this method from the same code behind file (the same window), everything is OK.
If I call this method from another window, using an instanse of the main window, ObservableCollection remains empty.:
SWindow sw = new SWindow();
sw.GetStateByDate(stringpar);
What is the reason for this? Does in this case method create yet another instance of ObservableCollection?
(I can see in debugger that sw._ShAvaQuCollection contains values. Is sw._ShAvaQuCollection not the same instanse of collection as _ShAvaQuCollection? If yes, how it can be resolved?)
Edited (added)
The ObservableCollection declared this way:
ObservableCollection<ShAvaQu> _ShAvaQuCollection =
new ObservableCollection<ShAvaQu>();
public ObservableCollection<ShAvaQu> ShAvaQuCollection
{ get { return _ShAvaQuCollection; } }
public class ShAvaQu
{
public string ShCode { get; set; }
public string SiID { get; set; }
public int PrCat { get; set; }
}
I call the method from a window, where another collection ShQuCollection displayed through ListView. In SelectionChanged event handler I take an argument for this database quering:
private void ShSelList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SWindow sw = new SWindow();
string str = sw.ShQuCollection[ShSelList.SelectedIndex].ShCode;
sw.GetStateByDate(str);
Close();
}
}
1) Most importantly you shouldn't be calling db logic from you windows / forms. You should abstract it out into another class. Then you could have your method return a observable collection.
However in your case I am assuming that you are trying to use the secondary form to reload / load the collection and you want it used on your primary form. The problem with this is you are creating a new instance of the form so your collection is being populated but not on your main form but a copy.
There are a couple ways you can try to get around that.
1) Make the method static and your observable collection static so that it updates a single instance.
2) Pass an instance handle of your primary form into your secondary form so that you re-use the instance you already have. This would be preferable so that you are not creating new instances all over the place.
In the constructor of the second form you could pass in the instance of your primary window so then you can use it directly. This should solve your problem.
UPDATE: Here is some code samples. Basically there are many ways to pass a reference.
You could do it like this with a constructor:
// This is the constructor for your second window
private Window _parentHandle;
public SecondWindow(Window obj)
{
this._parentHandle = obj;
}
Then from your primary form that has the method you would open that window like this.
SecondWindow w = new SecondWindow(this);
w.Show();
Now your second window has a direct handle to your first window so you can call your method on that variable and it will update.
Another way is to have a public Setter method on your second window as well.
public Window ParentContext
{
get { return this._parentHandle; }
set { this._parentHandle = value; }
}
Then you could create your form instance like this:
SecondWindow w = new SecondWindow(); // so just like normal
w.ParentContext = this; // set the instance to the calling form
w.Show();
That is the basics. This type of scenario works in just about any scenario where you need to pass a reference.
Hope that helps.

Categories

Resources