WPF:Unity: Reuse window after it has been closed - wpf

In ShellViewModel I have below command binding to open a new window "Remote View"
public ICommand RemoteViewCommand
{
get { return new RelayCommand(RemoteViewExecute, CanRemoteViewExecute); }
}
private void RemoteViewExecute()
{
if (!CanRemoteViewExecute())
{
return;
}
var shellRemoteView = Application._Container.Resolve<ShellRemoteView>();
if (_ShellRemoteView.DataContext==null)
_ShellRemoteView.DataContext = Application._Container.Resolve<ShellRemoteViewModel>();
shellRemoteView.Show();
}
On startup I have already registered both "ShellRemoteView" and "ShellRemoteViewModel" using lifetime managers to have singleton instance.
_Container.RegisterType<ShellRemoteView>(new ContainerControlledLifetimeManager());
_Container.RegisterType<ShellRemoteViewModel>(new ContainerControlledLifetimeManager());
When shellRemoteView.Show() executed and I close the form, then again on calling shellRemoteView.Show() I'm getting Invalid Operation excepton:Cannot set Visibility or call Show, ShowDialog, or WindowInteropHelper.EnsureHandle after a Window has closed.
Is there is any work-around in Unity to get window instance again if its closed.

This line is your problem:
return new RelayCommand(RemoteViewExecute, CanRemoteViewExecute);
Basically you are creating a new view each time you call the Get command. The way to fix this is to put a variable outside your GET statement that is scoped at the ViewModel level. Have it store a reference to the view and return that reference instead of creating a new reference each time. Look at the Singleton pattern for how best to do this.

You should register your View with LifetimeManager to create only one instance. Look at Using Lifetime Managers.

Related

Refreshing ViewModel's Entity Framework context

I'm using WPF, MVVM and Entity Framework in my current project.
To keep things simple, let's say I have a viewmodel for CRUD operations towards a list of materials (Solid woods).
My ViewModel's EF context (WTContext) is initialized through property injection, for instance:
SolidWoods_VM newView = new SolidWoods_VM();
newView.Context = new WTContext(SettingsManager.Instance.GetConnectionString());
This way I'm able to test this ViewModel:
SolidWoods_VM swVM = new SolidWoods_VM();
swVM.Context = new FakeWTContext();
Imagine that during a insert operation something goes wrong and the WTContext.SaveChanges() fails.
What is the best way to refresh the ViewModels context?
Create a new bool property in the viewmodel named ForTestingPurposes, and when the SaveChanges method fails:
try
{
Context.SaveChanges();
}
catch
{
if (!ForTestingPurposes)
{
Context = new WTContext(SettingsManager.Instance.GetConnectionString());
}
}
Send a message to the mainviewmodel for context reloading (through mediator pattern):
Mediator.Instance.NotifyColleagues<SolidWoods_VM>(MediatorMessages.NeedToUpdateMyContext, this);
(Yet, this way I'd still need the bool property)
3.A more elegant solution, without aditional properties, provided for you guys :)
Why not abstract the methods/properties you need on your data context onto an interface and then have an implementation of that that handles the exception.
//WARNING: written in SO window
public interface IDataSource
{
void SaveChanges();
//... and anything else you need ...
}
public class RealDataSource : IDataSource
{
private WTContext _context;
public void SaveChanges()
{
try { _context.SaveChanges(); }
catch
{
_context = new WTContext(/*...*/);
}
}
}
This way you can still implement a fake/mock data source but your view model class doesn't need to know anything about how the data is actually retrieved.
My opinion is that your best bet would be the message.
You need a way to indicate that the save went wrong, and it might not serve all consumers of the class to have the context regenerated. If you're binding to your VM in there, for example, resetting the context might have other UI consequences.

The calling thread cannot access this object because a different thread owns it

I am using the following code.
public partial class SettingApp
{
public SettingApp()
{
InitializeComponent();
Parallel.Invoke(SetDataInTextBox);
}
private void SetDataInTextBox()
{
txtIncAns.Text = Properties.Settings.Default.IncludeAN;
txtIncAuthor.Text = Properties.Settings.Default.IncludeAutt;
txtIncQuo.Text = Properties.Settings.Default.IncludeQU;
txtIncSpBegin.Text = Properties.Settings.Default.IncludeSP;
}
}
The program gives the following error
The calling thread cannot access this object because a different
thread owns it.
Which is the right way
update :
Whether this is right :
public partial class SettingApp
{
private delegate void SetDataInTextBoxDelegate();
public SettingApp()
{
InitializeComponent();
txtIncAns.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new SetDataInTextBoxDelegate(SetDataInTextBox));
}
private void SetDataInTextBox()
{
txtIncAns.Text = Properties.Settings.Default.IncludeAN;
txtIncAuthor.Text = Properties.Settings.Default.IncludeAutt;
txtIncQuo.Text = Properties.Settings.Default.IncludeQU;
txtIncSpBegin.Text = Properties.Settings.Default.IncludeSP;
}
}
Only the UI thread can access UI elements, which I'm guessing is what those txt things are. Parallel.Invoke is in your case not the UI thread, so the exception is thrown when you try to access the .Text property on the controls.
You need to marshal the call across to the UI thread. In WinForms, controls have various ways to help you do this:
if (myControl.InvokeRequired)
{
myControl.Invoke(...);
}
else
{
myControl.Text = "something";
}
MSDN has an article with examples on it here (VS2010):
http://msdn.microsoft.com/en-us/library/757y83z4(v=VS.100).aspx
Update 1:
For WPF the model is similar, but includes the Dispatcher:
myControl.Dispatcher.Invoke(...);
MSDN Forum Post
MSDN Article
Update 2: Of course, it looks like you don't even need to use multi-threaded code here. I would guess the overhead of using the multi-threaded portion is more than the code you eventually call. Simply remove the use of multiple threads from this section and set the properties directly:
public SettingApp()
{
InitializeComponent();
SetDataInTextBox();
}
private void SetDataInTextBox()
{
txtIncAns.Text = Properties.Settings.Default.IncludeAN;
txtIncAuthor.Text = Properties.Settings.Default.IncludeAutt;
txtIncQuo.Text = Properties.Settings.Default.IncludeQU;
txtIncSpBegin.Text = Properties.Settings.Default.IncludeSP;
}
As Adam said, only the UI thread can access UI elements. In the case of WPF, you'd use myControl.Dispatcher.Invoke().
Since these calls are all going to be invoked on the UI thread anyway, you should remove the Parallel.Invoke() and call the method directly.
Just an alternate suggestion. I would move toward databinding your textboxes to properties of a class, even if you don't want to go the full MVVM design, databinding is your friend in WPF. Then if you need the threading, WPF will handle updating controls on UI thread when the property changes, even when the property is changed on another thread.

MVVM Light, Windows Phone, View & ViewModel navigation between pages

I have a page where you basically select a set of options (configuration), and then you go to a next page, where you do some stuff
Using the MVVM Light toolkit, I have a viewmodel that binds to the view of the first page. when the user hits a button, it redirects to another view, which would be the 2nd page
i.e.:
Page2Command = new DelegateCommand((obj) =>
Messenger.Default.Send<Uri>(new Uri("/DoStuffView.xaml", UriKind.Relative),
Common.CommonResources.GoToDoStuffRequest)) });
The problem is, the viewmodel for the 2nd view (the way that I see it) has a couple of parameters in the constructor, which are basically the dependencies on the configuration that was set on the first page.
i.e. :
public DoStuffViewModel(ICollection<Note> availableNotes, SoundMappers soundType)
{
}
The problem lies here.. How can I instantiate the viewmodel with this data that was dynamically selected by the user on the 1st page?.
I can't use the ViewModelLocator pattern that MVVM light provides, since those viewmodels don't have any dependencies, they are just by themselves (or they can retrieve data from a db, file or whatever, but they don't have any dynamic input data). I could do it through the view's constructor, instantiate there the viewmodel, and assign to the view's DataSource the newly created viewmodel, but I think that's not very nice to do.
suggestions?
As I see you send messsage using Messenger class so you are familiar with messaging in MVVM light. You have to define your own message type that should accept your parameters from page 1:
public class Page2ViewModelCreateMessage : MessageBase
{
public ICollection<Note> AvailableNotes{get;set;}
public SoundMappers SoundType{get;set;}
public Page2ViewModelCreateMessage ()
{
}
public Page2ViewModelCreateMessage(ICollection<Note> availableNotes, SoundMappers soundType)
{
this.AvailableNotes = availableNotes;
this.SoundType = soundType;
}
}
You have to send an Page2ViewModelCreateMessage instance with you parameters and send it on navigating:
var message = new Page2ViewModelCreateMessage(myAvailableNotes, mySoundType)
Messenger.Default.Send(message);
On Page2 you have to register for recieving message of type Page2ViewModelCreateMessage:
Messenger.Default.Register<Page2ViewModelCreateMessage>(this, OnPage2ViewModelCreateMessage);
..
public void OnPage2ViewModelCreateMessage(Page2ViewModelCreateMessage message)
{
var page2ViewModel = new Page2ViewModel(messsage.AvailableNotes, message.SoundType);
}
As you can see I have replace your DoStuffViewModel with Page2ViewModel to be more clear.
I hope this will help you.
NOTE:I dont guarantee that code will work as its written in notepad.
The way I do this is to have a central controller class that the ViewModels all know about, via an interface. I then set state into this before having the phone perform the navigation for me. Each ViewModel then interrogates this central class for the state it needs.
There are a number of benefits to this for me:
It allows me to have non-static ViewModels.
I can use Ninject to inject the concrete implementation of the controller class and have it scoped as a singleton.
Most importantly, when tombstoning, I only need to grab the current ViewModel and the controller class.
I ran into a problem with messaging where my ViewModel was the registered listener, because I was View First and not ViewModel First, I was forced to use static ViewModel references. Otherwise the ViewModel wasn't created in time to receive the message.
I use the controller class in conjunction with messages (it is basically the recipient of all messages around the UI) so in future if I refactor, I don't need to change much, just the recipients of the messages.
Come to think of it, the controller class is also my navigation sink - as I have some custom navigation code that skips back paging on certain pages etc.
Here's an example of my current set up:
public interface IController
{
Foo SelectedFoo { get; }
}
public class ViewModel
{
private IController _controller;
public ViewModel(IController controller)
{
_controller = controller;
}
private void LoadData()
{
// Using selected foo, we load the bars.
var bars = LoadBars(_controller.SelectedFoo);
}
}
You could use PhoneApplicationService dictionary to save data you need when navigation from first event, and parse it when you navigateTo second page. you can also use that data in your ViewModels.
Something like this:
PhoneApplicationService.Current.State["DatatFromFirstPage"] = data;
and when navigating to second page:
if (PhoneApplicationService.Current.State.ContainsKey("DatatFromFirstPage"))
{
var dataUsedOnSeconPage= PhoneApplicationService.Current.State["DatatFromFirstPage"];
}
you can use this data globally in entire app

WPF / Prism library and multiple shells

I'm pretty new with Prism and after playing a bit around, there a few questions that arise. I'm trying to create a modular application that basically contains a map control in a shell window. The plugin modules offer different tools for interacting with the map. Some of the modules are pretty independent and simply display pins on the map.
1st question: How would RegionManager come into play for the module-specific classes (presenters) that must interact with the main map control? Usually in a RegionManager you register a specific view which is linked to a ViewModel, but in my case there is one single view (the map view) with multiple presenters acting on it.
2nd question: I need to be able to open several windows (shells) -- a bit like an MS Word document -- that should all be extended by the plugin modules. In a single-shell environment, when the module specific classes were instantiated, they could use the Dependency Injection Container to get a reference to the RegionManager or the Shell itself in order to get access to the map control. However with multiple shells, I don't see how to get access to the map control of the right shell. The dependency container has references to object global to the application, not specific for the shell I'm currently working in. Same is true for the EventAggregator.
Any input would be very welcome,
Ed
After hours of reading Prism-related articles and forums I've come across the article "How to build an outlook style application" on Erwin van der Valk's Blog - How to Build an Outlook Style Application.
In one part of the architecture, a Unity Child Container was used to resolve type instances. That's exactly what I needed for the answer to my 2nd question: I needed to have "scoped" (by window) dependency injection (ex: window scoped EventAggregator, Map control, etc.)
Here's how I create a new window:
private IShellWindow CreateNewShell(IRegionManager regionManager)
{
IUnityContainer childContainer = this.Container.CreateChildContainer();
... register types in child container ...
var window = new ShellWindow();
RegionManager.SetRegionManager(window, regionManager);
window.Content = childContainer.Resolve<MapDocumentView>();
return window;
}
So MapDocumentView and all its components will be injected (if needed) window-scoped instances.
Now that I can have scoped injected objects, I can get the window-scoped map in my module-based MapPresenter. To answer my 1st question, I defined an interface IHostApplication which is implemented by the Bootstrapper which has a MapPresenterRegistry property. This interface is added to the main container.
Upon initialization, the modules will register their presenters and upon the window creation, they will be instantiated.
So for the module initialization:
public void Initialize()
{
...
this.hostApplication.MapPresenterRegistry.Add(typeof(ModuleSpecificMapPresenter));
...
}
The code that initializes the map window:
private void View_Loaded(object sender, RoutedEventArgs e)
{
// Register map in the == scoped container ==
container.RegisterInstance<IMap>(this.View.Map);
// Create map presenters
var hostApplication = this.container.Resolve<IHostApplication>();
foreach (var mapPresenterType in hostApplication.MapPresenterRegistry)
{
var mapPresenter = this.container.Resolve(mapPresenterType) as IMapPresenter;
if (mapPresenter != null)
{
this.mapPresenters.Add(mapPresenter);
}
}
}
The module-specific MapPresenter:
public ModuleSpecificMapPresenter(IEventAggregator eventAggregator, IMap map)
{
this.eventAggregator = eventAggregator;
this.map = map;
this.eventAggregator.GetEvent<AWindowSpecificEvent>().Subscribe(this.WindowSpecificEventFired);
// Do stuff on with the map
}
So those are the big lines of my solution. What I don't really like is that I don't take advantage of region management this way. I pretty much have custom code to do the work.
If you have any further thoughts, I would be happy to hear them out.
Eduard
You have one main view and many child views, and child views can be added by different modules.
I'm not sure that the RegionManager class can be applied in this situation, so I would create a separate global class IPinsCollectionState
which must be registered as singleton in the bootstrapper.
public interface IPin
{
Point Coordinates { get; }
IPinView View { get; }
//You can use a view model or a data template instead of the view interface, but this example is the simplest
}
public interface IPinsCollectionState
{
ObservableCollection<IPin> Pins { get; }
}
Your main view model and different modules can receive this interface as a constructor parameter:
public class MapViewModel
{
public MapViewModel(IPinsCollectionState collectionState)
{
foreach (var item in collectionState.Pins)
{ /* Do something */ };
collectionState.Pins.CollectionChanged += (s, e) => {/* Handle added or removed items in the future */};
}
//...
}
Example of a module view model:
public class Module1ViewModel
{
public Module1ViewModel(IPinsCollectionState collectionState)
{
//somewhere in the code
collectionState.Pins.Add(new Module1Pin());
}
}
The second question can be solved in many different ways:
Application.Current.Windows
A global MainViewModel which contains the list of ShellViewModels and if you add new view model it will be displayed in new window. The bootstrapper is single for all windows.
Some kind of shared state which is passed to the constructor of the bootstrapper.
I don't know how these windows are related between themselves, and I don't know which way is the best, maybe it is possible to write an application with separated windows.

Error trying to bind Content to Window for 2nd time

have the following in my CodeBehind (class name MainHostWindow)
private object _hostContent = null;
public object HostContent
{
get { return _hostContent; }
set { _hostContent = value;}
}
this binds into a ContentControl of my View.
in a different class I do the following:
MainHostWindow host = new MainHostWindow();
{
host.HostContent = MyView; // this is an instance of a UserControl
host.Owner = this._mainWindow;
host.DataContext = viewModel;
}
host.ShowDialog();
first time it shows the MainHostWindow with the correct Content, 2nd time I get the following exception:
Specified element is already the logical child of another element. Disconnect it first.
It looks as if you are trying to add the same UserControl (not a new instance of it) to another instance of your MainHostWindow. The error is correct because the same element cannot be the child of two different containers (what would UserControl.Parent return?). You will need to create a new instance of your UserControl.
host.HostContent = new MyView();
are you able to set MyView declaratively in the XAML for MainHostWindow as this would always create a new instance when the Control is created.

Resources