I have a WPF application that provides navigation between few pages. The MainWindow is the Window object contains a Frame object. I then have few Page objects. I need to implement a StatusBar where some text will be updated (in a TextBlock) based on what action user has taken on a particular page.
Should my StatusBar be declared in the MainWindow or there is any better place for it?
How I will be able to access that TextBlock in StatusBar from various Pages?
What usually works for me is either pub-sub or dependency injection:
At first you might give your statusbar its own viewmodel. This would be composed into the shell view of your application, probably your MainWindow. I usually have a shell viewmodel comprising a toolbar or ribbon, a statusbar and, taking the remaining space, an IShellContent container. So, to answer your first question, I would declare it in its own view, give it its own viewmodel and compose it into your MainWindow.
The second problem can be solved in different ways:
Either give your statusbar viewmodel an interface, e.g. IStatusBar, and configure your dependency injection container to bind the viewmodel as singleton. Every viewmodel that needs to output status messages could use it via constructor injection, like this:
public MyViewModel(IStatusBar statusBar)
{
this.statusBar = statusBar;
statusBar.ShowMessage("Creating new MyViewModel...");
}
Or you could use a message bus infrastructure that comes with many MVVM frameworks today. Your statusbar viewmodel would subscribe a StatusMessage, and whenever something needs to post a status message it would create a new StatusMessage and publish it, like this:
public MyViewModel(IMessageBus bus)
{
this.bus = bus;
bus.Publish(new StatusMessage("Text"));
}
I would go for the first solution (dependency injection) because it is easier testable.
Related
I've created a dialog service using interfaces to show custom dialog/confirmation boxes (I know that Prism comes with this ability but the dialog boxes don't appear to be customizable and don't match my layout/color scheme). The dialog service has a register function that registers the dialog view type with dialog viewmodel type in a dictionary. This is done so that a viewmodel can be passed in and in a loosely coupled fashion, an instance of the needed view can be created. The code looks like this:
private readonly IDialogService dialogService = new DialogService(Application.Current.MainWindow);
// Registers the confirmation window viewmodel with the confirmation window view
dialogService.Register<ConfirmationWindowViewModel, ConfirmationWindow>();
so my initial thought was to try to create this in the unity bootstrapper (because of the registration passing in views and viewmodels) but I can't seem to find a way to do that and pass in the dialog service.
I must note that the constructor for the main window viewmodel also injects the region manager and the event aggregator for Prism; I had tried creating an instance in the bootstrapper and registering the instance but the creation of the region manager vs the injection causes errors. If I declare and initialize the dialog service in the main window viewmodel it of course works but from my understanding of MVVM we don't want the viewmodels to have any knowledge of the views so I'm trying to find another way to do it, without breaking IoC for region manager and event aggregator.
I am new to MVVM and Prism/Unity so my grasp of these concepts isn't fully solidified yet.
I know that Prism comes with this ability but the dialog boxes don't appear to be customizable and don't match my layout/color scheme
You can create whatever you like as dialog, just derive from PopupWindowAction and override CreateWindow (and other methods as needed) to create the dialog you always wanted.
In case anyone sees this later and is curious, my end decision was to get rid of the 'Register' function altogether in favor of a solid convention instead.
Previously, I would use this function and kept a dictionary of all the registered views/viewmodels:
dialogService.Register<ConfirmationWindowViewModel, ConfirmationWindow>();
this would register take the and store them in the dictionary so I could later pass in a viewmodel and create an instance of the appropriate confirmation message view. Instead I removed all code regarding this part of the solution and replaced it with some reflection mixed in with naming conventions.
Step 1: Ensure all views are named with the suffix View at the end.
Step 2: Ensure all viewmodels are named with the suffix ViewModel at the end.
Step 3: Ensure these are all in appropriately named namespaces (views in views namespace and viewmodels in viewmodels namespace).
(most of this ^^ is done anyway)
Final Step: Replaced dictionary with this code:
var viewTypeName = viewModel.GetType().AssemblyQualifiedName.Replace("Model", "");
var viewType = Type.GetType(viewTypeName);
in the dialog interface. Now, no matter what viewmodel is passed in, it will pull the appropriate view with less code and no necessary linking as was done before.
I am doing some data presentation using CM and WPF, and some of the data tabs have very similar formats but have to be kept in separate VM containing tabs as part of the standard for the application.
My initial thoughts were that I could do this programmatically in the VM by looking for any property pertaining to Views on the VM object (which itself is a Screen object derivation.) Its direct superclass is used as a contract for [ImportMany] so that the parent VM and View can tabulate the collection.
[ImportingConstructor]
public PartiesMasterPartiesViewModel(
IEventAggregator events,
IHelpService help,
ResourceManager<B_Action> actionResource,
IActionService actionService)
: base( events, help, actionResource, actionService)
{
}
protected override void OnActivate()
{
base.OnActivate();
this.Views.Add(new KeyValuePair<object, object>(this,
new PartiesMasterListView()));
}
So either I am not using this property correctly, or it does not do what I thought it does and I need to use another way.
Another way I'm thinking of doing it this explicitly instantiating multiple instances of the same viewmodel and manually adding them to the collection, but this seems like it would be violating what MEF's [ImportMany] would be here to do and weaken the design of the application.
The simplest way to achieve a view shared by multiple view models is to configure the ViewLocator with some extra rules.
In this example I have two view models Examples.ViewModels.SharedData1ViewModel and Examples.ViewModels.SharedData1ViewModel and a single view Examples.Views.SharedDataView that I'd like to be the view that Caliburn.Micro locates for both by default.
In my set up code I can add the following simple regular expression to the ViewLocator.
ViewLocator.NameTransformer.AddRule(
#"^Examples.ViewModels\.SharedData(\d+)ViewModel",
#"Examples.Views.SharedDataView");
I'm working on an application, and I'm using the MVVM approach.
Basically, there are currently two Pages, and 1 MainWindow.
I switch between the pages using a Frame inside MainWindow.
In the main window, there are 2 buttons which are basically global and should show in all pages; x (exit) and settings.
This is basically my 'shell', as I decided to not use a window border.
The problem is I'd like each page to have a different background and this is where it gets complicated:
- Settings page: Grey background.
- Main Page: Rotating background color that changes according to a property.
The thing is the background is being set in the main window, because it should apply to the global area as well (the top, where the exit and settings buttons are).
I first set the background (in MainWindow) as bound to a property the represents the current page (the value is then being translated into a color hex code with the help of a converter).
All in all, this results in a case where the background changes when a page is changed, but not when the property inside MainPage changes. I can clearly understand why, but I have no idea how to solve it.
The possible solutions I came up with so far:
Somehow causing the binding in MainWindow to update/refresh when the property is changed in MainPage.
Changing the background manually from inside each of the pages. (Although doesn't it negate the idea of mvvm?)
Move the background into each of the pages and set it from there, while making the global buttons on top of the page (which could be a bad thing in case controls end up overlapping).
If so, what would be the best solution to this problem?
If you haven't already, I'd suggest you install some package via NuGet to make MVVM style development more enjoyable. I personally prefer MVVMLight which is... well, light, but it also packs lot's of helpful features.
To communicate between ViewModels, you have (at least) two possible approaches.
1) ViewModelLocator (not recommended)
ViewModelLocator is central place holding references to all of your viewmodels. You could add a property that is then used by all of the viewmodels to get/set the background.
....
x:Name="Main"
DataContext="{Binding Source={StaticResource Locator}, Path=MainVM}">
....
<Grid Background="{Binding Background, Converter={StaticResource StringBrushConverter}}">
...
2) Messenger (recommended)
When ever property changes in your viewmodel(s) or method is executed, you could send a message that your MainViewModel is registered to listen to. Sending a message would be as easy as...
Messenger.Default.Send(new UpdateBackgroundMessage(new SolidColorBrush(Colors.Blue)));
And you'd register for this message in your MainViewModel's constructor:
Messenger.Default.Register<UpdateBackgroundMessage>(this, message =>
{
Background = message.Brush;
});
Actual message class would be:
public class UpdateBackgroundMessage : MessageBase
{
public UpdateBackgroundMessage(Brush brush)
{
Brush = brush;
}
public Brush Brush { get; set; }
}
I know I'm simplifying things here but I hope you got the idea. Both approaches are valid even if you decide not to use MVVMLight.
Edit:
Here's Git repo with example https://github.com/mikkoviitala/cross-viewmodel-communication
I think you should use Application Properties for storing background. There are various benefit of this :
1) Globally available
2) Easy to remember or store user preference
3) Automatically maintain separate profile for each user as it store values in AppData folder of user.
you can use Messenger to notify that background property has changed so that main window or shell could pull out new background value and update it.
I am currently trying to design an application that loads viewmodels through MEF imports.
So far so good, I navigate from viewmodel to viewmodel, having loaded each vm datatemplate through dictionaries.
Each time I navigate, I modify the content of the main contentPresenter in my Shell (MainWindow).
One of the viewmodel allows me to display a WindowFormHost for an activeX control (such as acrobat reader for example). Since WindowFormHost does not allow binding, I created the windowFormHost in the viewmodel and binded it to a ContentPresenter in the view.
And here is where it fails : when coming back to the same viewmodel, the view is created again... throwing a “Element is already the child of another element.” error.
How can I prevent that ? Should I unload WindowFormHost when view is reloaded ? Or Can I keep view instances so that I keep only one instance for each view and let data binding update controls ? (It looks better for memory consumption).
Thanks for your help !
[EDIT]
Loaded dictionary :
<DataTemplate x:Shared="False" DataType="{x:Type vm:DAVPDC3DVIAControlViewModel}">
<vw:MyUserControl />
</DataTemplate>
View :
<DockPanel>
<ContentControl Name="WFH3DVia" Content="{Binding Path=Control3DVIA, Mode=OneWay} </ContentControl>"
<!--<WindowsFormsHost Name="WFH3DVia"></WindowsFormsHost>-->
</DockPanel>
VM (singleton, mef module) :
[Export(typeof(IDAVPDC3DVIAControl))]
public partial class DAVPDC3DVIAControlViewModel : ViewModelBase, IViewModel, IPartImportsSatisfiedNotification
VM (main window)
[Export]
public class MainWindowViewModel : ViewModelBase, IPartImportsSatisfiedNotification
// CurrentUC binds main widow view to controller active viewmodel
public IViewModel CurrentUC
{
get
{
return myAddinManager.CurrentVM;
}
}
Main view :
Controler (displays module on event) :
private void ModuleReadyEventAction(string iModuleName)
{
if (null != this.Modules && this.Modules.Count() > 0)
{
foreach (var item in Modules)
{
IBaseModule ibasemodule = item as IBaseModule;
if (null != ibasemodule)
{
Type tp = ibasemodule.GetType();
if (0 == tp.Name.CompareTo(iModuleName))
{
CurrentVM = ibasemodule.GetViewModel();
break;
}
}
}
}
}
I'm also working on a project in WPF using Prism v4 and MVVM (except I'm using Unity). I also have at least two controls that I need to use which are Windows Forms controls that must be hosted in a WindowsFormsHost. Let me explain my thoughts on the process..
It seems to me, that you are trying to avoid any code in your View's code behind. That's the only reason I can think of that you are moving your WindowsFormsHost into your ViewModel. I think that this is fundamentally the wrong approach. The WindowsFormsHost exists for the reason of displaying a graphical Windows Forms control. Therefore, it belongs in the view!
Now, I understand the appeal of DataBindings. Trust me, I've wanted to able to DataBind many parts of my WindowForms control. Of course, to accept a WPF data binding the property must be a dependency property on a dependency object. The easiest solution, which is not unreasonable, is to simply add the code to configure your windows forms control in the code behind for your view. Adding your UI logic into your ViewModel is an actual violation of the MVVM design pattern, while adding code behind is not. (And in some cases is the best approach)
I've seen possible hacks to try to get around this limitation. Including using "proxies" which inject a databinding, or perhaps extending WindowsFormsHost and adding DependencyProperties which wrap a specific hosted control's properties, or writing classes using reflection and trying to throw in windows forms bindings. However, nothing I've seen can solve the problem completely. For example, my windows forms control can contain other graphical components, and those components would need to support binding as well.
The simplest approach is to simply synchronize your view with your viewmodel in your view's code behind. Your view model can keep the file or document that is open, filename, title, etc., but leave the display and display related controls up to the View.
Last, let me comment more directly on your question. I would need to see how you are registering your View and ViewModel with the MEF Container and how you are navigating to understand why you are receiving that error. It would seem to me that either your view or view model is getting created more than once, while the other is not. Are these registered as singleton types? Regardless, I stand by what I said about not including the WindowsFormsHost in your ViewModel.
In a MVVM WPF application.
How do you set a second windows parent from the ViewModel?
example:
view1 -- viewModel1
viewModel1's command calls:
var view2 = new view2
view2.Owner = <----This is the problem area. How do I get view1 as the owner here from the viewModel?
view2.Show()
EDIT:
See accepted answer below, then read the following edit.
I'am using MVVM light -> http://mvvmlight.codeplex.com/ (awesome btw)
The baked-in messaging system is great. I am now sending a message from the viewmodel to my view telling it to show another window.
For the message I'am currently using a string with a switch statement in the main view to determine what view to open; however I may tinker with the tokens that also are part of MVVM light toolkit.
Thank you!
In my opinion, opening a new window is the responsibility of the View, not of the ViewModel. Personally, I would use the same approach as used for displaying a dialog box (this was discussed in this forum already):
Have the ViewModel send a Message to the View requesting that it opens a new Window.
(alternatively) use an IDialogService or whatever you want to call it which you pass to the ViewModel's constructor. This service will be in charge of opening the Window (or of delegating this task to the View).
This way, you keep a clean separation of concerns and your VM remains testable (you can unit test that the request to open the new WIndow has been sent, but you couldn't test that the window has been, indeed, open).
Does that make sense?
Cheers,
Laurent
From your viewmodel call
Messenger.Default.Send<NotificationMessage>(new NotificationMessage("Open Window"));
And from your view's codebehind (a view that call the second
view) easily write this in the constructor:
Messenger.Default.Register<NotificationMessage>(this, ReplyToMessage);
And also write this method in the view's codebehind:
private void ReplyToMessage(NotificationMessage msg)
{
if (msg.Notification == "Open Window")
{
SecondWindow win = new SecondWindow();
win.ShowDialog();
}
}
I don't have an answer of my own but here's a few links to things I've been looking at lately that might help. I'll also be interested in anything others suggest.
As I understand it, the key thing is, you shouldn't be creating Views from within a View Model if possible, so you need a means of communicating what you need in a loosely coupled fashion.
http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
http://www.codeproject.com/KB/architecture/MVVM_Dialogs.aspx
Handling Dialogs in WPF with MVVM
You can do in this way like you need to create some events and register those in view and call these in view model.and open that pop up window.
Like This example
public class Mainclass : MainView
{
public delegate abc RegisterPopUp(abc A);
public RegisterPopUp POpUpEvent;
public RelayCommand ShowCommand { private set; get; }
public void ShowCommand()
{
ShowCommand("Your parameter");
}
}
inside the view
MainView mn = new MainView();
Register the event here like mn.POpUpEvent += then click on tab button double time and in registers popup method write the code for opening the pop up window.
Prism-Event Aggrigator is good approach, where we can create independent module without dependency. first viewmodel will publish event and then another view or view or viewmodel can subscribe that event from event aggrigator.
in this case Unity container can also use to inject one viewmodel in to another with dependency injection.