Syncing data between two Window in WPF - c#

I am developing an application in WPF. I need to load an instance of the Window class (which I call Win1 here) with which a form is filled. Then, when the Submit button is clicked, Win1 closes and only then can a new Win2 window be loaded (another class, also inherited from Window). The problem is that both of them open and I can not synchronize the data obtained from the first Win1 and pass them to the second Win2. I'm just messing up.
Someone can give me a generic idea indicating the tools and the pattern I need to do the above. For the specifications given to me, it is necessary that Win2 appears only after Win1 has finished its work.
Even though the application is more complex than I described it now, I would like to post some code, but I manage to confuse the ideas of who is reading me, so I tell you that at the moment I'm managing the windows inside the constructor of App.cs, while MainWindow.cs corresponds to Win2 and I created a new class to implement Win1.
public partial class App : Application
{
// Params...
public App()
{
Client = LoadNetwork();
User = LoadUser(Client); // Shows Win1
Games = LoadMinigames();
mainWindow = new MainWindow(User, Games);
Application.Current.MainWindow = mainWindow; // On XAML default is Hidden
mainWindow.Show(); // Shows Win2
}
// Other methods...
}
The biggest problem for me is to pass User data to MainWindow and I do not have many ideas on how to deal with this case.
Update
public partial class MainWindow : Window
{
public UserLoading ul;
public UserRegistering ur;
public User.UserProfile User;
private List<Game.Game> Games;
public Label Username;
public MainWindow(User.UserProfile user, List<Game.Game> games)
{
User = new UserProfile();
InitializeComponent();
User = user;
Games = games;
Username.Content = User.Username;
DrawList(Games);
}
//...
}
I realize I have explained myself a bit 'badly rereading my question several times. So I update it trying to be clearer by reporting here my answer to one of the comments.
The UserLoad method is not blocking, because inside it are instantiated classes that inherit Window (other windows for login and registration in other words) then the flow of execution proceeds and instantiates the MainWindow where naturally the argument "user" will result null because the forms have not been filled yet. I realize now that perhaps I had explained myself badly. The call of Win1 is not blocking and I would like it to return only when the user data is ready to be passed as an argument to Win2.

I have done this in the past. here is my solution:
Set Your Launch Window to Win1. Let It launch. Create a Static Method in App.cs to launch Win2. When Win1 is ok to shut down and you want Win2 to open call App.ShowMainWindow(this) from within Win1.
App.cs
public partial class App : Application
{
static internal void ShowWin2(Win1 win1)
{
Win2 win2 = new Win2();
// Copy Win1 stuff to Win2 here
Application.Current.MainWindow = win2;
win2.Show();
}
}
Win1
public partial class Win1 : Window
{
public Win1()
{
InitializeComponent();
}
private void CloseAndLaunchWin2()
{
App.ShowWin2(this);
this.Close();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
CloseAndLaunchWin2();
}
}

As User Nawed mentioned, you should read into MVVM. Syncing can be achieved by using the same model for two different views.
You could do something like this.
var sharedContext = new MyViewModel();
var viewOne = new MyWindow();
var viewTwo = new MyUserControl();
viewOne.DataContext = viewTwo.DataContext = sharedContext;

Related

Displaying a WPF window from an Installer class

I'm having a similar problem as this issue. I'm trying to display a WPF window from an Installer class from System.Configuration.Install. My window, corresponding to my software license manager window, should ideally pop up during or after the installation process to install the license as well. However, the installation finishes without the window ever showing and I'm not sure why.
Here is my code:
[RunInstaller(true)]
public partial class Installer1 : System.Configuration.Install.Installer
{
public Installer1()
{
InitializeComponent();
}
// INSTALL EVENT //////////
public override void Install(System.Collections.IDictionary stateSaver)
{
StaThreadWrapper(() =>
{
LicenseActivationWindow activationWindow = new LicenseActivationWindow();
activationWindow.ShowDialog();
});
}
// Method to call the xaml in a thread safe way
private static void StaThreadWrapper(Action action)
{
var t = new Thread(o =>
{
action();
System.Windows.Threading.Dispatcher.Run();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
// UNINSTALL EVENT //////////
public override void Uninstall(System.Collections.IDictionary stateSaver)
{
}
}
I had to add the StaThreadWrapper method to fix the "The calling thread must be STA, because many UI components require this." error from calling the wpf window directly from the installer thread and I'm no longer getting the error message but I'm also not getting the window to show up. I thought this would solve the issue like [1] but it didn't.
What am I doing wrong?
Displaying a window in a System.Configuration.Install.Installer is probably not a very good idea but if you still want to try it out, then create an Application class on the STA thread. Something like this:
var t = new Thread(o =>
{
var app = new System.Windows.Application();
app.Run(new LicenseActivationWindow());
});
t.SetApartmentState(ApartmentState.STA);
t.Start();

MVVM How to set datacontext when viewmodel uses async

After hours of searching I am still without answer to this question. I have read this nice writing about async MVVM and made my viewmodel to use factory method.
public class MainViewModel
{
// sic - public, contrary to the pattern in the article I cite
// so I can create it in the Xaml as below
public MainViewModel()
{
}
private async Task InitializeAsync()
{
await DoSomethingAsync();
}
public static async Task<MainViewModel> CreateAsync()
{
var ret = new MainViewModel();
await ret.InitializeAsync();
return ret;
}
}
This is clear for me, but I can't understand how to make instance of MainViewModel and set it to datacontext in MainPage. I can't simply write
<Page.DataContext>
<viewModel:MainViewModel/>
</Page.DataContext>
because I should use MainViewModel.CreateAsync()-method. And I can't do it on code-behind, which I even want to do, because code-behind -constructor is normal method, not an async-method. So which is proper way to continue?
made my viewmodel to use factory method
I'm normally a fan of that approach - it's my favorite way to work around the "no async constructors" limitation. However, it doesn't work well in the MVVM pattern.
This is because VMs are your UI, logically speaking. And when a user navigates to a screen in an app, the app needs to respond immediately (synchronously). It doesn't necessarily have to display anything useful, but it does need to display something. For this reason, VM construction must be synchronous.
So, instead of trying to asynchronously construct your VM, first decide what you want your "loading" or "incomplete" UI to look like. Your (synchronous) VM constructor should initialize to that state, and it can kick off some asynchronous work that updates the VM when it completes.
This is not too hard to do by hand, or you can use the NotifyTaskCompletion approach that I described in an MSDN article on async MVVM data binding to drive the state transition using data bindings.
You have to initalize the viewmodel before the window is open. Go to your App.xaml file and remove the part: StartupUri="MainWindow.xaml". Then you go to the App.xaml.cs and add this:
protected async override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainWindow = new MainWindow { DataContext = await CreateAsync() };
mainWindow.Show();
}
I would re-factor. Make the MainViewModel construction / instantiation lightweight. Then create a Load or Initialize method on your VM. From the code-behind create an instance, set it to the DataContext, then invoke the init method and let it run.
E.g.
/// <summary>Interaction logic for MainWindow.xaml</summary>
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
var dc = new MainViewModel();
dc.Initialize("Hello", " ", "world");
this.DataContext = dc;
}
}
public class MainViewModel
{
/// <summary>Simple constructor</summary>
public MainViewModel() { }
public void Initialize(params object[] arguments)
{
//use the task to properly start a new thread as per:
//http://stackoverflow.com/a/14904107/1144090 and
//https://msdn.microsoft.com/en-us/library/hh965065.aspx
//(what would happen if we simply invoke init async here?)
this.InitializeAsync(arguments)
.ContinueWith(result =>
{
if (!result.IsFaulted)
return;
MessageBox.Show("Unexpected error: " + Environment.NewLine + result.Exception.ToString());
});
}
private async Task InitializeAsync(params object[] arguments)
{
await Task.Delay(2333);
MessageBox.Show(String.Concat(arguments));
}
}
Note that this is the quick-and-dirty solution, the other two answers (paired with a dependency injection framework) will give you proper high-level structure for your solution.
Firstly, you should make default constructor as private to avoid misusing your class (the article you cite does this - the constructor is private).
The approach you are using to set DataContext is not suitable for MVVM pattern (the View shouldn't create its ViewModel itself).
You should create your View and ViewModel in the higher level layer and have that layer bind them. Says if the Page is your main View you should create them in App.xaml.cs by overriding OnStartup, something like this:
var page = new Page();
var dataService = new YourDataService(); // iff Create or the ctor require arguments
var viewModel = await MainViewModel.CreateAsync(dataService);
page.DataContext = viewModel;
page.Show();

WPF maintain window size across multiple windows

I am developing a WPF application (using MVVM) which consists of several windows. All windows have the same dimensions specified and open at the centre of the owner screen. The user may also resize the windows. I now require 2 things.
To maintain the same size across all windows in case the user resizes any of the windows.
To maintain the same position across all windows in case the user drags any of the windows on the screen.
For example, consider the following workflow: MainWindow -> ChildWindow1 -> ChildWindow2. On a button click in the MainWindow, ChildWindow1 opens. On a button click in ChildWindow1, ChildWindow2 opens. The windows open on top of each other, and once you close a window, the previous window would be shown. Suppose the user now resizes ChildWindow2. I want the same to be reflected across MainWindow and ChildWindow1 as well, such that when the user closes ChildWindow2, ChildWindow1 would be of the same size as that of the resized ChildWindow2. This would give users the impression that they're working in the same window.
Also, if the user drags any of the Windows, I want the position of the parent windows to change and correspond to that of the child window.
How can I achieve both these things?
I would create a class where you can subscribe and invoke actions. This class can look something like:
internal class ActionService<T>
{
private static ActionService<T> instance;
private readonly List<ServiceAction<T>> registeredActions;
private ActionService()
{
registeredActions = new List<ServiceAction<T>>();
}
internal static ActionService<T> Instance()
{
return instance ?? (instance = new ActionService<T>());
}
internal void Subscribe(string actionName, Action<T> action)
{
registeredActions.Add(new ServiceAction<T>(actionName, action));
}
internal void Unsubscribe(string actionName)
{
registeredActions.RemoveAll(action => action.ActionName == actionName);
}
internal void Invoke(string actionName, T parameter)
{
foreach (ServiceAction<T> action in registeredActions.Where(action => action.ActionName == actionName).ToArray())
{
action.Action.Invoke(parameter);
}
}
private class ServiceAction<TSub>
{
internal ServiceAction(string actionName, Action<TSub> action)
{
ActionName = actionName;
Action = action;
}
internal string ActionName { get; private set; }
internal Action<TSub> Action { get; private set; }
}
}
In the constructor of each ViewModel you can call something like:
ActionService<Thickness>.Instance.Subscribe("SizeChanged", SizeChaned);
The second parameter is a function so you have to add the following to your ViewModel:
private void SizeChanged(Thickness thickness)
{
// Change the size to the passed value
}
Now if anyone changes the size of any window you can call:
ActionService<Thickness>.Instance.Invoke("SizeChanged", ACTUALSIZE_AS_THICKNESS);
You can use this ActionService anywhere you want to communicate over ViewModel- or Model-Borders.
The way I would approach this is using WPF Behaviors, which allow you to encapsulate UI-specific (i.e. non-View Modelish) behavior.
I don't know your exact requirements here (are these the only windows in the application? Are they always synchronized?) but the general approach would be to create a SynchronizedWindowBehavior which I would attach to the different Windows.
This behavior will access some sort of central WindowSynchronizationService, an event aggregator or singleton service (presumably registered in your DI container) which publishes decoupled events. Each instance of the behavior listens to the attached window's Move and Resize events and publishes an event on the aggregator. The other behavior instances consume this event and resize/move their attached windows accordingly.
Here's a good tutorial, both for defining Blend behaviors, hooking them up to a window, and also (specifically) listening to Window resize events: http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35

WPF Windows Controller

I am having some problems with WPFs.
I have a project that has multiple windows, so to control this windows, I have created a controller class. This controller will have a instance of each windows:
this.mainWindow = new MainWindow();
this.loginWindow = new LoginWindow();
this.registerWindow = new RegisterWindow();
The problem comes when I callback from any of the windows to the controller class and from this controller I want to update the information of the window (for example update the value of a property), the information is not being updated
// In controller
public void login(String email, String pass)
{
....
this.loginWindow.showErrorInPassword();
}
// In LoginWindow
public void showErrorInPassword()
{
this.emailErrorImage.Visibility = System.Windows.Visibility.Visible;
}
... but if I send from the LoginWindow a reference of itself to the login function on the controller, the emailErrorImage will be shown
public void login(String email, String pass, LoginWindow lw)
{
....
lw.showErrorInPassword();
}
Seems that the instance that I have in the controller is not the same as the one that is being displayed when I do this.loginWindow.show()
Can someone tell me what am I doing wrong?
You are going to need to bind the UI objects to a MVVM class to update each window.
Use events to call back to the controller.
Here is a brief example. First create a class to contain event args. Doesn't really have to contain anything. It just differentiates between different delegates. Make it its own class in the namespace so everything has access to it.
public class SomeEventArgs: EventArgs
{
}
Inside the window class:
public event EventHandler<SomeEventArgs> CallBackToController;
protected virtual void OnCallBackEvent(object sender, SomeEventArgse)
{
EventHandler<SomeEventArgs> handle = CallBackToController;
if (handle != null)
{
handle(this, e);
}
}
In the controller class, after instantiating the window assign the event to a method.
this.loginWindow = new LoginWindow();
this.loginWindow.CallBackToController += new EventHandler<SomeEventArgs>(MethodToHandleEvent);
Then the Method must have the same form as expected:
private void MethodToHandleEvent(object sender, SomeEventArgs e)
{
// Do something in response.
}
Now anytime you call OnCallBackEvent(this, new SomeEventArgs()) from the window class, the controller class will catch the event and execute MethodToHandleEvent
For instance:
private void LoginWindowBtn_Click(object sender, RoutedEventArgs e)
{
// Logged in ok, let the controller know.
OnCallBackEvent(this, new SomeEventArgs ());
}
There are a ton of tutorials on this, I think this is a better approach to passing references of windows from window to window.

Why I can't see my Window on the plug in area?

I'm not able to see my Window in the plug in area. I know that some code must be added in the Integrate section. However i don't know which.
public void Integrate() { }
public void IntegratePresentation() {}
How can I make it visible? Is it really possible?, if not How can I display a window in Petrel?
You should add your window to the system so that Petrel displays it when requested to.
Your window class should be a ToggleWindow:
public class MyWindow : ToggleWindow{
...
}
Add a menu through which you can ask Petrel to create and open your window in the Windows area:
public void IntegratePresentation()
{
WellKnownMenus.Window.AddTool(
new PetrelButtonTool("&My Window",
PetrelImages.Editor,
(sender, e) => PetrelProject
.ToggleWindows
.Add(new MyWindow())));
}
I hope this helps.

Categories

Resources