prism - IsNavigationTarget not invoked when using RequestNavigate - wpf

I am trying to learn the Prism Navigation support. Presently, I have a prism Region and I want to load view to that region using RegionManager.RequestNavigate(). The navigation does occur, however the IsNavigationTarget() of INavigationAware is not invoked, even if the ViewModel of the Navigation Target view implements INavigationAware interface. Here is the code that I am using.
Shell:
<StackPanel Margin="10">
<TextBlock Text="Main Window"/>
<Button Content="RegionA" Command="{Binding NavigateToACommand}" />
<ContentControl prism:RegionManager.RegionName="MainRegion"/>
</StackPanel>
ShellViewModel:
private void NavigateToA () {
Uri uri = new Uri("RegionAView", UriKind.Relative);
RegionManager.RequestNavigate("MainRegion", uri);
}
RegionAView:
<UserControl x:Class="NavigationExample.RegionAView"
<Grid>
<TextBlock Text="This is Region A"/>
</Grid>
</UserControl>
RegionAViewModel
public class RegionAViewModel : INavigationAware{
public RegionAViewModel() {
}
public bool IsNavigationTarget(NavigationContext navigationContext) {
return false; //Not Invoked
}
public void OnNavigatedTo(NavigationContext navigationContext) {
//Gets Invoked
}
}
RegionAView.xaml.cs
[Export("RegionAView")]
public partial class RegionAView : UserControl {
public RegionAView() {
InitializeComponent();
}
}
Why does the IsNavigationTarget() not getting invoked prior to completion of Navigation?

I think your problem is that you export your view as singleton. modify VM and V as follow:
[Export("RegionAView")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class RegionAView : UserControl
{
public RegionAView()
{
InitializeComponent();
}
}
Basically, IsNavigationTarget will be invoked when you have existing instances. But it will not work for newly created instance.

Related

Nest reactiveui usercontrols wpf / pass ViewModel to usercontrol

Q: How can I bind a ViewModel to a ReactiveUserControl? Or how to nest Reactiveui views?
There's probably something I'm doing wrong, but I can't figure out what exactly.
ReactiveUserControl
// MenuView.xaml
<reactiveui:ReactiveUserControl
x:Class="Views.MenuView"
xmlns:menuItems="clr-namespace:Model"
.... >
<Menu x:Name="RootMenu"
IsMainMenu="True">
<Menu.Resources>
<DataTemplate DataType="{x:Type menuItems:DialogItem}">
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</Menu.Resources>
</Menu>
</reactiveui:ReactiveUserControl>
// MenuView.xaml.cs
namespace Views
{
public partial class MenuView : ReactiveUserControl<MenuViewModel>
{
public MenuView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel,
vm => vm.MenuItems,
view => view.RootMenu.ItemsSource
).DisposeWith(disposables);
});
}
}
}
// MenuViewModel.cs
namespace Views
{
public class MenuViewModel : ReactiveObject
{
public ObservableCollection<DialogItem> MenuItems { get; } = new ObservableCollection<DialogItem>();
public MenuViewModel()
{
MenuItems.Add(new DialogItem("Edit", 224));
MenuItems.Add(new DialogItem("View", 224));
}
}
}
DialogItem represents an item in the menu
// DialogItem.cs
namespace Model
{
public class DialogItem
{
public DialogItem(string description, int dialogId)
{
this.DialogId = dialogId;
this.Description = description;
}
public int DialogId { get; }
public string Description { get; }
}
}
Then finally in MainWindow I include the usercontrol like so:
// MainWindow.xaml
<reactiveui:ReactiveWindow
x:Class="Views.MainWindow"
....
>
<Grid>
<views:MenuView x:Name="MainMenu" />
</Grid>
</reactiveui:ReactiveWindow>
Code behind
// MainWindow.xaml.cs
namespace Views
{
public partial class MainWindow : ReactiveWindow<MainWindowModel>
{
public MainWindow()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
// BIND THE VIEWMODEL CREATED IN THE MAINWINDOW VIEWMODEL, IS THIS CORRECT?
this.Bind(ViewModel,
vm => vm.MainMenuViewModel,
view => view.MainMenu.ViewModel
).DisposeWith(disposables);
});
}
}
}
// MainWindowModel.cs
namespace Views
{
public class MainWindowModel : ReactiveObject
{
public MenuViewModel MainMenuViewModel { get; }
public MainWindowModel()
{
this.MainMenuViewModel = new MenuViewModel();
}
}
}
The Items are iterated, but if I look at the visual representation tree I an ViewModelViewHost item in the ContentPresenter instead of a TextBlock
Here you see the Menu Items are not rendered correctly. (they are rendered but without the text from DataTemplate.
Update:
Using a Menu.ItemTemplate does work, but this is not what I am looking for.
<reactiveui:ReactiveUserControl
x:Class="Views.MenuView"
...
>
<Menu x:Name="RootMenu"
IsMainMenu="True">
<Menu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</Menu.ItemTemplate>
</Menu>
</reactiveui:ReactiveUserControl>
Discussing this issue on Slack with Glenn Watson gave me the penny drop moment. Because I was using the code behind binding of RxUI, RxUI is using its locator logic to lookup the view. RxUI will lookup a view if there's no ItemTemplate or DisplayPathMember property defined. These views are registered in the Splat container as a view for the viewmodel. So the DataTemplates in <Menu.Resources> are not considered. This is exactly what's shown in the image. A ViewModelViewHost is created, but because no corresponding view is found nothing is displayed.
This can be solved by using the XAML binding instead of the code behind binding (assign the viewmodel to the datacontext to do this!). See the docs for more info.

Mvvm - How to capture in the ViewModel, which Button was pressed, using dataBinding command and Parameter Command? What am I not getting?

To simplify, Criticized for writing a novel w/no code a month ago, I made a quick wpf project (uses MVVM) with 2 buttons on the UI.
When a button is clicked, I need my ViewModel to know which one, to route the Speech Synthesizer to the correct Text to Speak. Thanks 4 any help!!
Simple UI Image
<Window x:Class="Wpf_School_Announce.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wpf_School_Announce"
xmlns:vm="clr-namespace:Wpf_School_Announce.ViewModels"
mc:Ignorable="d"
Title="Announcements" Height="236.436" Width="293.218">
<Window.Resources>
<vm:ViewModelBase x:Key="viewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Source=viewModel}">
<StackPanel Margin="0,10">
<Button x:Name="btn1stBell" Content="1st Bell" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="0,10"
Command="{Binding ParameterCommand, Source={StaticResource viewModel}}"
CommandParameter="{Binding Command, ElementName=btn1stBell}"/>
<Button x:Name="btnLunchMenu" Content="Lunch Menu" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="0,10"
Command="{Binding ParameterCommand, Source={StaticResource viewModel}}"
CommandParameter="{Binding Command, ElementName=LunchMenu}"/>
</StackPanel>
</Grid>
</Window>
namespace Wpf_School_Announce.ViewModels
{
public class ViewModelBase
{
public ParameterCommand ParameterCommand { get; set; }
public ViewModelBase()
{
ParameterCommand = new ParameterCommand(this);
}
public void ParameterMethod(string <Not sure what needs to go here>)
{
Debug.WriteLine("Parameter Comand:{0}", AnnoucementModel);
//Todo: Need to find out which UI button was clicked to direct The Speech Synthesozer to the correct Speech Text.
}
}
}
namespace Wpf_School_Announce.ViewModels.Commands
{
public class ParameterCommand : ICommand
{
public ViewModelBase ViewModel { get; set; }
public ParameterCommand(ViewModelBase viewModel)
{
ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ViewModel.ParameterMethod(parameter as String);
}
}
}
It is a bad solution to just have one command on your viewmodel and to bind every button to it. If you have different things to be executed, define different commands. For that you either have to define a separate class with a dedicated Execute metod for each command or you can use something like RelayCommand of MvvmLight, where you can pass delegates upon creation of each command like this
public class ViewModelBase
{
public RelayCommand BellCommand...
public RelayCommand LunchCommand...
public ViewModelBase()
{
this.BellCommand = new RelayCommand(this.ExecuteBell);
this.LunchCommand = new RelayCommand(this.ExecuteLunch);
}
private void ExecuteBell(object Parameter) {...}
private void ExecuteLunch(object Parameter) {...}
}
and in your XAML
<Button Command="{Binding Path=BellCommand}"... />
<Button Command="{Binding Path=LunchCommand}" ... />
This way you have separate places for the individual logic and your viewmodel must not know anything about your ui - which is good.
Hope it helps.
XAML:
<Button CommandParameter="command_name" Command="{Binding OnClick}" Content="Click Me"></Button>
Event.cs:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
private ICommand onClick;
public ICommand OnClick
{
get
{
return onClick ?? (onClick = new RelayCommand(clickSwitch));
}
}
Class.cs:
private async void clickSwitch(System.Object obj)
{
switch (obj.ToString())
{
case "command_name":
//code
break;
}

DependencyProperty.RegisterAttached and Multiple Instances

I'm working on a WPF MVVM application. I'm looking to databind a WebBrowser control to a view model which is in turn bound to a Tab. Following the advice in this article, I created a static helper class consisting of a static DependancyProperty:
public static class WebBrowserHelper
{
public static readonly DependencyProperty BodyProperty =
DependencyProperty.RegisterAttached("Body", typeof(string), typeof(WebBrowserHelper), new PropertyMetadata(OnBodyChanged));
public static string GetBody(DependencyObject dependencyObject)
{
return (string)dependencyObject.GetValue(BodyProperty);
}
public static void SetBody(DependencyObject dependencyObject, string body)
{
dependencyObject.SetValue(BodyProperty, body);
}
private static void OnBodyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
string newValue = (string)e.NewValue;
var webBrowser = (WebBrowser)d;
webBrowser.NavigateToString(newValue);
}
}
XAML Binding WebBrowser to DependancyProperty:
<WebBrowser Grid.Column="2" HorizontalAlignment="Center" src:WebBrowserHelper.Body="{Binding HTMLBody}" VerticalAlignment="Center" Height="Auto" Width="Auto" />
ViewModel that bound to ItemsSource of Tab Control:
public class SomeVM : ViewModelBase, INotifyPropertyChanged
{
private string _htmlBody;
private SomeView _myView = new SomeView();
public SomeVM (string tabName)
{
TabName = tabName;
string contentsAsHTML = do_a_whole_bunch_of_stuff_to_generate_an_HTML_string();
HTMLBody = contentsAsHTML;
}
public string HTMLBody
{
get { return _htmlBody; }
set
{
if (_htmlBody != value)
{
_htmlBody = value;
RaisePropertyChanged("HTMLBody");
}
}
}
public SomeView View
{
get {return _myView;}
set { }
}
public string TabName { get; set; }
}
MainViewModel, Creating the Tab collection:
private ObservableCollection<SomeVM> _tabs;
public ObservableCollection<SomeVM> Tabs
{
get
{
if (_tabs== null)
{
_tabs= new ObservableCollection<SomeVM>();
_tabs.Add(new SomeVM("Tab 1"));
_tabs.Add(new SomeVM("Tab 2"));
_tabs.Add(new SomeVM("Tab 3"));
}
return _tabs;
}
}
MainWindow.xaml setting up the Tab Binding:
<TabControl ItemsSource="{Binding Tabs, Source={StaticResource vm}}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding TabName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding View}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
My problem is that "OnBodyChanged" is fired multiple times on ever tab change. The HTML takes a few seconds to load, and I would rather it only loads when the property is actually modified in the viewmodel.
EDIT
Here's the smallest sample project that recreates my problem.
Your problem is not relevant to attached properties or MVVM.
In fact, the real problem is that TabControl destroy and recreate its child every time you change the selected tab. That would explain why the handler is invoked more than once. The VisualTree only contains the selected Tab.
If you can try with another control, you will see there are no errors.
For solving this issue, I will redirect you to this post.

WPF Binding To Application Properties

I need to bind to static properties in my App.xaml.cs class and have so far done this using:
Property="{Binding Source={x.Static Application.Current}, Path=SomePath}"
This works OK when the application is being run directly, but when the application is started from another application, these bindings don't work as I believe Application.Current then points at the parent application rather than the application that the xaml sits under.
How would I bind to the immediate App.xaml.cs file properties rather than those from the parent application?
Hope that makes sense!
So one solution I've found so far is to put a class between App.xaml.cs and the XAML I'm trying to bind:
App.xaml.cs:
public partial class App : Application
{
public static string SomeText;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
SomeText = "Here is some text";
}
}
MyProperties.cs:
public class MyProperties
{
public static string SomeText
{
get { return App.SomeText; }
}
}
MainWindow.xaml:
<Window.Resources>
<local:MyProperties x:Key="properties"/>
</Window.Resources>
<Grid>
<TextBlock Text="{Binding Source={StaticResource properties},
Path=SomeText}"/>
</Grid>
Any other suggestions are still more than welcome :)
App.xaml.cs:
public partial class App : Application
{
public static string SomeText => "Here is some text";
}
MainWindow.xaml:
<Grid>
<TextBlock Text="{Binding Source={x:Static Application.Current},
Path=SomeText}"/>
</Grid>

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.

Resources