Caliburn.Micro: Communication between user controls - wpf

I'm building my first Caliburn WPF application, and I find myself in the following problem.
I have a parent view, with loads two user controls: Search & Results. When the search button is clicked on the Search user control, I wan't to load the results in the results user control.
Parent View:
<ContentControl x:Name="SearchViewModel"/>
<ContentControl x:Name="ResultsViewModel"/>
Parent VM
[Export(typeof(IMainViewModel))]
public class ParentViewModel : Screen, IMainViewModel{
public SearchViewModel SearchViewModel { get; set; }
public ResultsViewModel ResultsViewModel { get; set; }
public ParentViewModel()
{
SearchViewModel = new SearchViewModel();
ResultsViewModel = new ResultsViewModel();
}
}
Search View
<TextBox x:Name="Term"/>
<Button Content="Search" x:Name="Search"/>
Search VM
public class SearchViewModel : PropertyChangedBase
{
private string _term;
public string Term
{
get { return _term; }
set
{
_instrumentId = value;
NotifyOfPropertyChange(() => _term);
}
}
public void Search()
{
//Call WCF Service
//Send results to results user control?
}
}
So actually how can i pass or access data/methods between different user controls / view models with caliburn micro?

You can use events via the Caliburn Micro Event Aggregator. You can publish an event in one viewmodel and subscribe that event in the other. This keep the model decoupled - the only coupling is done by the event itself-, in which you can store the data to transfer.

Related

WPF: Data binding to common property across views

I have a WPF application, where I want to keep a centralized date selection. I want to allow the date to set through one screen, and update it on others. Below is the common service,
public interface IDateService
{
public DateTime ScheduledDate { get; set; }
}
public sealed class DateService : ObservableObject, IDateService
{
private DateTime _scheduledDate = DateTime.Now.AddDays(1);
public DateTime ScheduledDate
{
get => _scheduledDate;
set
{
SetProperty(ref _scheduledDate, value);
}
}
}
I inject this though the constrictor of the view models of each screen.
public DateSetViewModel( IDateService dateService, IDialogCoordinator dialogCoordinator)
{
_dateService = dateService;
}
public DateTime ScheduledDate
{
get => _dateService.ScheduledDate;
set
{
_dateService.ScheduledDate = value;
}
}
and on read only views
public class DateReadViewModel : ObservableObject
{
private readonly IDateService _dateService;
public DateReadViewModel( IDateService dateService, IDialogCoordinator dialogCoordinator)
{
_dateService = dateService;
}
public DateTime ScheduledDate
{
get => _dateService.ScheduledDate;
}
...
}
Now, when loading, all screen shows initial date (now +1 day). Any update made through DateSetViewModel is reflected on that page UI. But, when switch to other views, it always shows initial date, not the updated value from IDateService. I tried to directly bind to dateService.ScheduledDate in other views, but it didn't work. I use MahApps.Metro to define the views if that matters.
The bindings on DateSetView
<DatePicker Width="100"
Margin="{StaticResource ControlMargin}"
SelectedDate="{Binding ScheduledDate}" />
and other views, I tried few, but similar to
<DatePicker Width="100"
Margin="5"
mah:TextBoxHelper.AutoWatermark="True"
SelectedDate="{Binding ScheduledDate, Mode=OneWay}" />
<TextBlock
Margin="5"
VerticalAlignment="Center"
Text="{Binding ScheduledDate}"
/>
DateService is the only object that raises the PropertyChanged event when its ScheduledDate property is set.
Given your current implementation, you need to subscribe to this event in the view models and raise the event for each data-bound source property:
public class DateReadViewModel : ObservableObject
{
private readonly IDateService _dateService;
public DateReadViewModel(IDateService dateService, IDialogCoordinator dialogCoordinator)
{
_dateService = dateService;
_dateService.PropertyChanged += OnDateServicePropertyChanged;
}
private void OnDateServicePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(IDateService.ScheduledDate))
OnPropertyChanged(nameof(ScheduledDate));
}
public DateTime ScheduledDate
{
get => _dateService.ScheduledDate;
}
...
}
public interface IDateService : INotifyPropertyChanged
{
public DateTime ScheduledDate { get; set; }
}
The other option is to bind directly to the property of the service.
They all work initially because the binding will fetch the value at the time the views are instantiated. Binding dependency properties (like the Text of the TextBlock and the SelectedDate of the DatePicker) though requires some sort of notification so the Binding(Expression) knows to refetch the (updated) value from the source. While your DateService may be sending a notification, those objects you are exposing the DateTime from (and which is the source for the binding) are not (e.g. the DateReadViewModel or DateSetViewModel). You should make those classes provide a notification using one of the mechanisms documented - see the Binding Sources and Targets section of this page for a description of those mechanisms. Basically make ScheduledDate a dependency property, implement a ScheduledDateChanged event -or- implement INotifyPropertyChanged. Or I suppose you could expose the Service itself from the view model but that to me is exposing an internal implementation detail and should be avoided.

OnNavigatedTo is not called in TabControl when selecting tab when using PRISM regions

I have registered my views for the TabControl with Region manager and views are shown properly when tab is selected.
The problem is that when I select new tab item OnNavigatedTo is not called for that view or its view model.
I'm using PRISM 6.3
UPDATE
ViewModel
`public class ValuationViewModel : IViewModel, INavigationAware
{
private IRegionManager _regionManager;
public string Title { get; set; }
public ValuationViewModel(IRegionManager regionManager)
{
Title = "PERFORM VALUATION";
_regionManager = regionManager;
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}`
View
`public partial class ValuationView : UserControl, IView
{
private IRegionManager _regionManager;
public ValuationView(ValuationViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public IViewModel ViewModel
{
get
{
return (IViewModel)DataContext;
}
set
{
DataContext = value;
}
}
}`
Without code, nobody can give you the correct answer.
Its probably the best, if you show us your ViewModel for your "TabItem" View.
Assuming you registered your view and set your ViewModel in DataContext correctly, it could be possible that forget just a simple thing.
To manage your problem make sure you implemented the following things correctly:
Create a region for your TabControl
Register your view in that region
Make sure the DataContext is correctly set to your ViewModel
Make sure your ViewModel implemented INavigationAware
Update 1:
After testing a lot I found a simple answer unfortunately:
Members of INavigationAware (OnNavigatedTo, IsNavigationTarget & OnNavigatedFrom) are called when the NavigationService is navigating.
They aren't if you click on the TabItemHeader.
To solve your problem you have several options.
One option is to start a navigation request when the user click on the TabItemHeader ( bad approach).
In my opinion you should use the IActiveAware Interface ( https://msdn.microsoft.com/en-us/library/microsoft.practices.prism.iactiveaware(v=pandp.50).aspx).
It will solve your problem, because the navigation via RegionManager and the clicking on the TabItemHeader results in the same: INavigationAware.IsActive = true.
Now you are able to detect when your tab is shown or not and react.

Loading Views into ContentControl and changing their properties by clicking buttons

I have a mvvm(model view viewmodel) silverlight application that has several views that need to be loaded into ContentControls (i made it all in expression blend). What i dont know how to do is, for example, to load one view (user control) in one content control by clicking a button from another view that is in another content control. To make it easier to understand the problem, i need to do something similar to this:
http://www.codeproject.com/KB/silverlight/BlendableVMCom.aspx
with that difference that child1 and child2 are supposed to be loaded into theirown content controls by clicking Call child1 or call child2 buttons.
and example would be appreciated. Thanks in advance!
This example is very simplified, but I think you now how to adjust it to your application.
The main view:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border x:Name="commandsView">
<Button Content="Call view 1" Command="{Binding CallView1Command}" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="5" />
</Border>
<Border x:Name="displayedView" Grid.Column="1">
<ContentControl Content="{Binding CurrentView}" />
</Border>
</Grid>
I haven't created separated views as user controls, here are just borders, which can be replaced by real views.
Different view models for different views in code behind:
this.commandsView.DataContext = new CommandsViewModel();
this.displayedView.DataContext = new DisplayedViewModel();
First view model conains the command which sends the message to another view model:
public class CommandsViewModel
{
public CommandsViewModel()
{
this.CallView1Command = new RelayCommand(() =>
Messenger.Default.Send<View1Message>(new View1Message()));
}
public RelayCommand CallView1Command { get; set; }
}
public class View1Message : MessageBase
{
}
To make this example work, download the MVVM Light library.
The second view model receive the message and creates a view for its property:
public class DisplayedViewModel : ViewModelBase
{
public DisplayedViewModel()
{
Messenger.Default.Register<View1Message>(this, obj =>
this.CurrentView = new TextBlock { Text = "Pressed the button 1 and now here is the view 1" });
}
private object currentView;
public object CurrentView
{
get { return currentView; }
set
{
currentView = value;
RaisePropertyChanged("CurrentView");
}
}
}
Again, it is possible to use clr object instead of controls and apply data templates in xaml, but there will not be enough space to provide all the resulting code.
So that is all, the main idea is a some kind of event aggregator, which is the Messenger class in this particular case.
Without the MVVM Light it will require more code:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
var events = new GlobalEvents();
this.commandsView.DataContext = new CommandsViewModel(events);
this.displayedView.DataContext = new DisplayedViewModel(events);
}
}
public class GlobalEvents
{
public event EventHandler View1Event = delegate { };
public void RaiseView1Event()
{
View1Event(this, EventArgs.Empty);
}
}
/// <summary>
/// Commands which call different views
/// </summary>
public class CommandsViewModel
{
public CommandsViewModel(GlobalEvents globalEvents)
{
this.CallView1Command = new DelegateCommand(globalEvents.RaiseView1Event);
}
public DelegateCommand CallView1Command { get; set; }
}
/// <summary>
/// Model where views are changed and then displayed
/// </summary>
public class DisplayedViewModel : INotifyPropertyChanged
{
public DisplayedViewModel(GlobalEvents globalEvents)
{
globalEvents.View1Event += (s,e) =>
this.CurrentView = new TextBlock { Text = "Pressed the button 1 and now here is the view 1" };
}
private object currentView;
public object CurrentView
{
get { return currentView; }
set
{
currentView = value;
RaisePropertyChanged("CurrentView");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In this example you must change the DelegateCommand class for something different. Other code will work for everyone.
It sounds like you might be trying to do some sort of navigation. If that's true, check out the Silverlight navigation framework.

WPF MVVM - using model in view model class

I would like to know what is correct using of model class in in view model. As MVVM I use Caliburn Micro.
First alternative.
Model class:
public class CurrentUser : IDataErrorInfo
{
public string Nick { get; set; }
public string Password { get; set; }
//...
}
Using model in view model class:
[Export(typeof(ILogOnViewModel))]
public class LogOnViewModel : Screen
{
public CurrentUser CurrentUser { get; set; }
//bind on control in view
public string CurrentNick
{
get { return CurrentUser.Nick; }
set
{
CurrentUser.Nick = value;
NotifyOfPropertyChange(() => CurrentNick);
}
}
//bind on control in view
public string CurrentPassword
{
get { return CurrentUser.Password; }
set
{
CurrentUser.Password = value;
NotifyOfPropertyChange(() => CurrentPassword);
}
}
}
Second alternative:
Model class:
public class CurrentUser : IDataErrorInfo, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public string Nick
{
get { return _nick; }
set
{
_nick = value;
NotifyPropertyChanged("Nick");
}
}
public string Password
{
get { return _password; }
set
{
_password = value;
NotifyPropertyChanged("Password");
}
}
//...
}
Using model class in view model class:
[Export(typeof(ILogOnViewModel))]
public class LogOnViewModel : Screen
{
//bind on UI control
public CurrentUser CurrentUser { get; set; }
}
The first alternative would be better, since it encapsulates your model better from the View.
But you should implement IDataErrorInfo and INotifyPropertyChanged on the ViewModel, since the ViewModel should be the object that notifies your user interface of changes and errors.
I would prefer the first approach. There are a few reasons why:
A Model should never be accessible to View.
In theory, a ViewModel wraps/facades all properties required to be bound to View from Model. It adds any additional properties, collections and commands required to facilitate View's functionality and while preventing putting code in code behind.
IDataErrorInfo and INotifyPropertyChanged facilitate View not ViewModel. And since View only communicates with ViewModel, they should be inside ViewModel.
I would use the second approach. If you are looking for sample applications that use the second approach then you might find the WPF Application Framework (WAF) project interesting.
I'd recommend starting off with the second approach. It could save you from typing out a lot of repetitive bridging properties. If you encounter a property that needs to be wrapped on the View Model, then do so for that property then update the View's binding(s). Both your Model and View Model can implement IDataErrorInfo and INotifyPropertyChanged. The latter is quite useful when some logic in your Model changes a property since it will then be propagated to the View. Implementing those interfaces via base classes, you could have both a ModelBase and a ViewModelBase abstract classes, where the latter derives from the former.

Accessing unity container in view model class

I have a shell which looks like toolbar and defines my main region (a wrap panel). What I need to do is be able to add widgets to the shell and when a widget is clicked, a new window (view) is opened. Below is what I have so far:
I created a module class which adds a view to the main region:
public class MyModule : IModule
{
protected IUnityContainer Container { get; private set; }
public MyModule(IUnityContainer container)
{
Container = container.CreateChildContainer();
}
public void Initialize()
{
var regionManager = Container.Resolve<IRegionManager>();
MyModuleView myModuleView = new MyModuleView();
regionManager.Regions["MainRegion"].Add(myModuleView);
}
}
Here is the content of MyModuleView:
<Grid>
<Grid.DataContext>
<vm:MyModuleVM/>
</Grid.DataContext>
<Button Content="My Module" Foreground="White" FontWeight="Bold" Command="{Binding Path=LaunchCommand}">
</Button>
</Grid>
The view model, MyModuleVM:
class MyModuleVM : ObservableObject
{
protected IUnityContainer Container { get; private set; }
public MyModuleVM()
{
}
RelayCommand _launchCommand;
public ICommand LaunchCommand
{
get
{
if (_launchCommand == null)
{
_launchCommand = new RelayCommand(() => this.LaunchTestView(),
() => this.CanLaunchTestView());
}
return _launchCommand;
}
}
private void LaunchTestView()
{
TestView view = new TestView();
view.Title = "Test View";
var regionManager = Container.Resolve<IRegionManager>();
regionManager.Regions["MyWindowRegion"].Add(view);
}
private bool CanLaunchTestView()
{
return true;
}
}
So my plan was as follows:
Create the class that implements
IModule (MyModule) and have it load a
view (MyModuleView) into the shell
when initialized
Create a view model for the module
(MyModuleVM) and set it as the
DataContext of the view displayed in
the shell
MyModuleVM contains a command that a
button in MyModuleView binds to.
When the button is clicked the
command is triggered
Now, here is where I am stuck. Using
a WindowRegionAdapter (an adapter
that helps to create views in
separate windows) I wanted to create
and display a new view. As seen in
MyModuleVM, LaunchTestView needs
access to the container in order to
add the view to a region. How am I
supposed to get to the container?
Besides my specific question about accessing the container, how is my overall strategy of adding "widgets" to a toolbar shell and launching
views when they are clicked? Am I comlpetely off track here when it comes to MVVM with Prism?
Thanks guys.
You can get the container injected through constructor or property injection. To do that, the ViewModel instance must be resolved by the container, or the BuildUp method should be called after it has been instantiated.
I hope this helps.
Thanks,
Damian

Resources