In my project I use Prism for the Views and ViewModels. I now want to load another view into a UserControl in the MainWindowView. I read I can do this with this:
_regionManager.RegisterViewWithRegion("MainRegion", typeof(View));
But unfortunately I have no idea how to get to the instance of IRegionManger in my ViewModel. In all examples I found, other variables are used, but it is not shown where they come from.
This is my View:
<Window x:Class="PortfolioVisualizer.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:PortfolioVisualizer"
mc:Ignorable="d"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="15*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<Button Command="{Binding NavigateCommand}" CommandParameter="AddAssetView">
<StackPanel>
<Image/>
<Label Content="Add Asset"/>
</StackPanel>
</Button>
<Button Command="{Binding NavigateCommand}" CommandParameter="ConfigView">
<StackPanel>
<Image/>
<Label Content="Settings"/>
</StackPanel>
</Button>
</StackPanel>
<Grid Grid.Column="1">
<ContentControl prism:RegionManager.RegionName="MainRegion"/>
</Grid>
</Grid>
</Window>
This is my ViewModel:
public class MainWindowViewModel : ViewModelBase
{
private readonly IRegionManager _RegionManager;
public DelegateCommand<string> NavigateCommand;
public MainWindowViewModel(IRegionManager regionManager)
{
_RegionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(ExecuteNavigateCommand);
_RegionManager.RegisterViewWithRegion("MainRegion", typeof(DashboardView));
}
private void ExecuteNavigateCommand(string viewName)
{
if (string.IsNullOrWhiteSpace(viewName))
return;
_RegionManager.RequestNavigate("ContentRegion", viewName);
}
}
This is the ViewModdelBase
public class ViewModelBase : BindableBase
{
public ViewModelBase()
{
}
}
(I know that the ViewModelBase is just superfluous, but there is something to come later)
You have the container inject the region manager like any other dependency:
internal class MyViewModel
{
public MyViewModel( IRegionManager regionManager )
{
regionManager.DoSomeStuff(); // or just put it into a field for later use
}
}
Note that this only works automatically, if you don't manually new the view model either in code or in xaml. Instead, create it with a factory that is itself injected (e.g. Func<MyViewModel> myViewModelFactory) if you go view model-first (recommended most of the time), or use Prism's ViewModelLocator to have it created as data context if you go view-first.
Related
I'm new to WPF and I'm having trouble binding text to a user control I made. It's a simple control, it's just basically a button with text and a image that I want to reuse in several Views.
Here is my User Control's .cs
public partial class MenuItemUserControl : UserControl
{
public string TextToDisplay { get; set; }
public MenuItemUserControl()
{
InitializeComponent();
DataContext = this;
}
}
Here is my User Control's xaml
<UserControl x:Class="Class.Controls.MenuItemUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Class.Controls"
mc:Ignorable="d"
d:DesignHeight="83.33" d:DesignWidth="512">
<Grid>
<Button Style="{StaticResource MenuItemStyle}" Height="Auto" Width="Auto">
<DockPanel LastChildFill="True" Width="512" Height="83.33" HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Source="/Resources/MenuArrow.png" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Center" DockPanel.Dock="Left"/>
<TextBlock d:Text="sample" Text="{Binding TextToDisplay, Mode=OneWay}" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="{StaticResource MenuItems}"/>
</DockPanel>
</Button>
</Grid>
</UserControl>
Here is my View xaml
<Page x:Class="Class.Views.MenuOperate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:uc="clr-namespace:Class.Controls"
xmlns:local="clr-namespace:Class.Views"
xmlns:properties="clr-namespace:Class.Properties"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" xmlns:viewmodels="clr-namespace:Class.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:MenuOperateViewModel}"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="1024"
Title="MenuOperate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
<RowDefinition Height="83.33"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="512"/>
<ColumnDefinition Width="512"/>
</Grid.ColumnDefinitions>
<uc:MenuItemUserControl TextToDisplay="{Binding StartStop, Mode=TwoWay}" Grid.Row="0" Grid.Column="0"/>
</Grid>
</Page>
Here is my View Model .cs
namespace Class.ViewModels
{
public class MenuOperateViewModel : ObservableObject
{
private string? _StartStop;
public MenuOperateViewModel()
{
StartStop = Properties.Resources.MenuOperateStart;
}
public string? StartStop
{
get => _StartStop;
set => SetProperty(ref _StartStop, value);
}
}
}
This is the error I get in my View Xaml:
Object of Type 'System.Windows.Data.Binding' cannot be converted to type 'System.String'.
There are two things that prevent that the expression
TextToDisplay="{Binding StartStop}"
works.
The target property of the Binding, i.e. TextToDisplay must be a dependency property.
You must not explicity set the UserControl's DataContext. The Binding will resolve the source property path relative to the current DataContext, i.e. with DataContext = this; in the control's constructor, it expects the source property StartStop on the UserControl, which is obviously wrong.
For details, see Data binding overview
Your code should look like this:
public partial class MenuItemUserControl : UserControl
{
public static readonly DependencyProperty TextToDisplayProperty =
DependencyProperty.Register(
nameof(TextToDisplay),
typeof(string),
typeof(MenuItemUserControl));
public string TextToDisplay
{
get { return (string)GetValue(TextToDisplayProperty); }
set { SetValue(TextToDisplayProperty, value); }
}
public MenuItemUserControl()
{
InitializeComponent();
}
}
The Binding in the UserControl's XAML would then use a RelativeSource Binding.
<TextBlock Text="{Binding TextToDisplay,
RelativeSource={RelativeSource AncestorType=UserControl}}" />
I have a ContentControl in DayView.xaml whose content binds to the CurrentSongViewModel property in DayViewModel.cs . The CurrentSongViewModel is DataTemplated in the ContentControl to display its respective view depending on the view model (either DefaultSongViewModel or EditSongViewModel), and both of the DataTemplates are confirmed to work. When I click the 'Edit' button on DefaultSongView.xaml, the EditSongCommand executes and sets the CurrentSongViewModel to a new EditSongViewModel. The CurrentSongViewModel setter calls OnPropertyChanged(), but the ContentControl content is not updating! I have set break points on the OnPropertyChanged() call and it is calling it. I have no idea why its not updating...
DayView.xaml
<UserControl x:Class="Calandar.Views.DayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Calandar.Views"
xmlns:viewmodels="clr-namespace:Calandar.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:DayViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightSteelBlue">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Day" Grid.Row="0" FontSize="35" FontFamily="Yu Gothic UI Semibold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" >
<Border Style="{StaticResource PurpleBorder}">
<!-- The binding that isnt working -->
<ContentControl Content="{Binding CurrentSongViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:DefaultSongViewModel}">
<local:DefaultSongView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:EditSongViewModel}">
<local:EditSongView/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Border>
</StackPanel>
</Grid>
</Grid>
</UserControl>
DefaultSongView.xaml
<UserControl x:Class="Calandar.Views.DefaultSongView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Calandar.Views"
xmlns:viewmodels="clr-namespace:Calandar.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:DayViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.DataContext>
<viewmodels:DayViewModel/>
</Grid.DataContext>
<StackPanel>
<DockPanel >
<Button Content="Edit" Command="{Binding EditSongCommand}"
Style="{StaticResource CollectionModifierButton}" DockPanel.Dock="Right"/>
<TextBlock Text="Songs" Style="{StaticResource BoxTitleText}" DockPanel.Dock="Top"/>
</DockPanel>
<ListBox ItemsSource="{Binding SongList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=.}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</UserControl>
DayViewModel.cs
public class DayViewModel : ViewModelBase
{
// content view models
private ViewModelBase _currentSongViewModel;
public ViewModelBase CurrentSongViewModel
{
get { return _currentSongViewModel; }
set
{
_currentSongViewModel = value;
OnPropertyChanged(nameof(CurrentSongViewModel));
}
}
// Song list
public ObservableCollection<string> SongList { get; set; }
// Commands
public ICommand EditSongCommand => new EditSongsCommand(this);
// Constructor
public DayViewModel()
{
_currentSongViewModel = new DefaultSongViewModel();
SongList = new ObservableCollection<string>();
foreach (string line in File.ReadLines(#"C:\Users\person\source\repos\Calandar\DataFiles\Songs.txt"))
{
SongList.Add(line);
}
}
}
EditSongCommand.cs
public class EditSongsCommand : CommandBase
{
DayViewModel _vm;
public override bool CanExecute(object parameter) => true;
public override void Execute(object parameter)
{
_vm.CurrentSongViewModel = new EditSongViewModel();
}
public EditSongsCommand(DayViewModel vm)
{
_vm = vm;
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
The following markup sets the DataContext to another instance of the DayViewModel:
<Grid.DataContext>
<viewmodels:DayViewModel/>
</Grid.DataContext>
You should remove it from DefaultSongView.xaml and bind to the command of the parent view model using a RelativeSource:
<Button
Content="Edit"
Command="{Binding DataContext.EditSongCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}"
I am trying to make an app using using MVVMLight that has a sidebar with buttons, each button shows a new usercontrol hosted in a ContentControl. When the user clicks the buttons in the sidebar (Show View1 / Show View2) it shows the views correctly.
My problems arise when the user has already shown a View (it could be View1 or View2 ), say View1 and click (Show View1) ** again ** View1 controls remain with the same values and I would like to show the same view with all controls as if it had restarted. (I know that I could reset all controls within the user control (View1) but my application requires having a new instance of the view every time the button is clicked).
Somehow, in the same scenario when the user is in View 1, switch to view 2 and return to view 1, a new instance is created and shown as I would like. I would like to get the same result without the need to switch to view 2 and return.
Here is a minimal reproducible example of the issue:
ViewModelLocator.cs
using CommonServiceLocator;
using GalaSoft.MvvmLight.Ioc;
namespace SidebarApp.ViewModel
{
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// Register viewmodels
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<View1ViewModel>();
SimpleIoc.Default.Register<View2ViewModel>();
}
public MainViewModel Main { get { return ServiceLocator.Current.GetInstance<MainViewModel>(); } }
public View1ViewModel View1Vm { get { return ServiceLocator.Current.GetInstance<View1ViewModel>(); } }
public View2ViewModel View2Vm { get { return ServiceLocator.Current.GetInstance<View2ViewModel>(); } }
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
}
MainViewModel.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Windows.Input;
namespace SidebarApp.ViewModel
{
public class MainViewModel : ViewModelBase
{
// Commands
public ICommand ShowView1Command { get; private set; }
public ICommand ShowView2Command { get; private set; }
/// <summary>
/// Property that will cointain the current viewmodel to show
/// ViewModelBase inplements ObservableObject class which imlements INotifyPropertyChanged
/// </summary>
public ViewModelBase CurrentViewModel { get; set; }
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
this.ShowView1Command = new RelayCommand(ShowView1);
this.ShowView2Command = new RelayCommand(ShowView2);
}
private void ShowView1()
{
// I tried this but it doesn't work
//CurrentViewModel = null or View2ViewModel;
CurrentViewModel = new View1ViewModel();
}
private void ShowView2()
{
CurrentViewModel = new View2ViewModel();
}
}
}
MainWindow.xaml
<Window x:Class="SidebarApp.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:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding Main, Source={StaticResource Locator}}"
Title="MainWindow" Height="348.965" Width="560.683">
<Window.Resources>
<DataTemplate DataType="{x:Type local:View1ViewModel}">
<local:View1View />
</DataTemplate>
<DataTemplate DataType="{x:Type local:View2ViewModel}">
<local:View2View />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Button Command="{Binding ShowView1Command}">Show View1</Button>
<Button Command="{Binding ShowView2Command}">Show View2</Button>
</StackPanel>
<ContentControl Grid.Column="1" Content="{Binding Path=CurrentViewModel}"></ContentControl>
</Grid>
</Window>
View1View.xaml
<UserControl x:Class="SidebarApp.View1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding View1Vm, Source={StaticResource Locator}}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightPink">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 1" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="Original text value"/>
<CheckBox Content="Some boolean value"/>
</StackPanel>
</Grid>
</UserControl>
View1ViewModel.cs
using GalaSoft.MvvmLight;
namespace SidebarApp
{
public class View1ViewModel : ViewModelBase
{
public View1ViewModel()
{
}
}
}
View2View.xaml
<UserControl x:Class="SidebarApp.View2View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SidebarApp"
mc:Ignorable="d"
DataContext="{Binding View2Vm, Source={StaticResource Locator}}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightCyan">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 2" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="Some text in view2"/>
<Button Content="Button"/>
</StackPanel>
</Grid>
</UserControl>
View2ViewModel.cs
using GalaSoft.MvvmLight;
namespace SidebarApp
{
public class View2ViewModel : ViewModelBase
{
public View2ViewModel()
{
}
}
}
App.xaml
<Application x:Class="SidebarApp.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SidebarApp" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:SidebarApp.ViewModel" />
</ResourceDictionary>
</Application.Resources>
</Application>
For example I enter to the view1 and change the value of the textbox ("Original text value") to another value and I click again in Show View1 the text and everything in the usercontrol stays the same but when I switch to view2 and go back to view1 a new usercontrol is shown with his original values.
This might feel like something of a workaround, but it works. Hopefully it will prove useful. (Obviously make same changes to View2View view/view model).
ViewModelLocator.cs
public View1ViewModel View1Vm { get { return new View1ViewModel(); } }
MainViewViewModel.cs
private void ShowView1()
{
CurrentViewModel = new ViewModelLocator().View1Vm;
}
View1View.xaml
<UserControl x:Class="SidebarApp.View1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SidebarApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="LightPink">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Text="Welcome to View 1" FontSize="20"/>
<TextBox HorizontalAlignment="Stretch" Text="{Binding Path=View1Text, Mode=TwoWay}"/>
<CheckBox Content="Some boolean value" IsChecked="{Binding Path=CheckBoxChecked, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</UserControl>
View1ViewModel.cs
public class View1ViewModel : ViewModelBase
{
private string _view1Text;
private bool _checkBoxedChecked;
public string View1Text
{
get
{
return _view1Text;
}
set
{
_view1Text = value;
RaisePropertyChanged("View1Text");
}
}
public bool CheckBoxChecked
{
get
{
return _checkBoxedChecked;
}
set
{
_checkBoxedChecked = value;
RaisePropertyChanged("CheckBoxChecked");
}
}
public View1ViewModel()
{
View1Text = "Original text value";
}
}
I'm having some trouble gaining a working understanding of how Region Navigation works in Prism. I'm trying to create an MVVM based app that loads a main window, and displays a view generated by a login form. After the login form is submitted, I then want to navigate to a new UserControl. I'd like to know if this is also possible without using modules, however for the current implementation, it's modular.
With this current code, the menu bar with a button displays, but not the Login view.
Main Module
App.xaml.cs
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
}
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
moduleCatalog.AddModule<LoginModule.ModuleLoginModule>();
}
MainWindow.xaml:
<Window x:Class="PrismMVVM.Views.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:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:local="clr-namespace:PrismMVVM"
mc:Ignorable="d"
Title="PrismMVVM" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<Button Content="Code is Poetry" HorizontalAlignment="Left" Width="Auto"/>
</DockPanel>
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion">
</ContentControl>
</Grid>
</Window>
MainWindowViewModel.cs
namespace PrismMVVM.ViewModels
{
class MainWindowViewModel : BindableBase
{
public IRegionManager _regionManager;
public MainWindowViewModel(IRegionManager regionManager)
{
_regionManager = regionManager;
regionManager.RequestNavigate("LoginRegion", "Login");
}
}
}
Login Module
ModuleLoginModule.cs:
namespace LoginModule
{
public class ModuleLoginModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("LoginRegion", typeof(Login));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<Login>();
}
}
}
Login.xaml:
<UserControl x:Class="LoginModule.Views.Login"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:LoginModule.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="White" prism:RegionManager.RegionName="LoginRegion">
<StackPanel Panel.ZIndex="1" Margin="150">
<TextBox HorizontalAlignment="Center" VerticalAlignment="Center">Text</TextBox>
<PasswordBox HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Button Background="LightBlue" Content="Login" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
<Rectangle Panel.ZIndex="0" HorizontalAlignment="Center" VerticalAlignment="Center" Fill="LightGray" Height="300" Width="400" />
</Grid>
</UserControl>
I'd like to know if this is also possible without using modules
For sure it is. Modules are completely optional, you can do all the registrations from the bootstrapper / PrismApplication, if you like.
There's nothing wrong with something like this:
public class MyApplication : PrismApplication
{
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<Login>();
}
}
internal class MainWindowViewModel
{
public MainWindowViewModel( IRegionManager regionManager )
{
regionManager.RequestNavigate( "ContentRegion", "Login" );
}
}
I'm currently developing in WPF (Surface 2.0) and using the MVVM pattern for most parts of my application. I am, unfortunately, currently facing a rather complicated issue I hope you guys can assist me on:
I have a View and a ViewModel that belongs to it. The View contains a two-way binding to a property in the ViewModel:
<pb:PivotBar ItemsSource="{Binding PivotBarEntries}"
SelectedItemIndex="{Binding SelectedPivotItemIndex, Mode=TwoWay}" />
(...)
<local:SomeOtherView />
While the View is first loaded, the setter of SelectedPivotItemIndex is called. This is fine, except that the setter is called before the rest of the view loaded. Since the setter sends messages (via MVVMLight's Messenger) to other viewmodels that are created later in the view, this is a problem - those messages never reach their destination since no receiver is registered for them so far.
public int SelectedPivotItemIndex
{
get
{
return this.selectedPivotItemIndex;
}
set
{
if (value != this.selectedPivotItemIndex)
{
this.selectedPivotItemIndex = value;
this.ReportPropertyChanged("SelectedPivotItemIndex");
(...)
ChangeSomeOtherViewModelProperty msg = new ChangeSomeOtherViewModelProperty { Property = newValueCalculatedBefore };
Messenger.Default.Send<ChangeSomeOtherViewModelProperty>(msg);
}
}
}
The only solution I can think of right now, would be to create a LoadedEventHandler in the ViewModel and call the SelectedPivotItemIndex setter again. I don't really like that, though:
For once, the setter runs again (which creates a rather large collection that is passed to the message). Don't know if it would really impact performance, but still seems unnecessary.
Secondly, it just seems kind of hackish and error prone to me, since every property has to be initialized manually in the loaded event.
Is there any solution to this problem better than just manually calling the setter?
i dont have a tutorial for viewmodel first, but i'm sure the are a lot examples out there. viewmodel first is nothing more then you have the viewmodel instance first and then let wpf create the view(via datatemplate).
let say your mainview should show a view with your PivotBarEntries. so what you do now is to create a pivotbarviewmodel in your mainviewmodel (DI, MEF, new() what ever). your mainviewmodel expose the pivotvw as a property and bind it to a ContentPresenter.Content in your mainview. at least you have to create a DataTemplate for your pivotvw DataType.
<DataTemplate DataType="{x:Type local:PivotViewModel>
<view:MyPivotView/>
</DataTemplate>
thats about viewmodel first, you do not rely on load events on view anymore, because your vm is created first.
of course for your specific problem you just have to be sure that all your components(VM's) which listen to your messenger should be created
your xaml
<ContentPresenter Content="{Binding MyPivotDataVM}" />
<ContentPresenter Content="{Binding MySomeOtherStuffVM}" />
instead of view first
<pb:PivotBar ItemsSource="{Binding PivotBarEntries}"
SelectedItemIndex="{Binding SelectedPivotItemIndex, Mode=TwoWay}" />
(...)
<local:SomeOtherView />
EDIT: very simple example for viewmodel first. ps: i use DI with MEF to create my object path.
app.xaml
<Application x:Class="WpfViewModelFirst.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfViewModelFirst="clr-namespace:WpfViewModelFirst">
<!--StartUp Uri is removed-->
<Application.Resources>
<!--comment these datatemplates and see what happens-->
<DataTemplate DataType="{x:Type WpfViewModelFirst:PivotViewModel}">
<WpfViewModelFirst:PivotView/>
</DataTemplate>
<DataTemplate DataType="{x:Type WpfViewModelFirst:OtherViewModel}">
<WpfViewModelFirst:OtherView/>
</DataTemplate>
<DataTemplate DataType="{x:Type WpfViewModelFirst:OtherChildViewModel}">
<WpfViewModelFirst:OtherChildView/>
</DataTemplate>
</Application.Resources>
</Application>
app.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
//to be fair, sometimes i create the ApplicationRoot(JUST MainWindow with view first, and just the rest with viewmodel first.)
var mainvm = new MainViewModel();
var mainview = new MainWindow {DataContext = mainvm};
this.MainWindow = mainview;
this.MainWindow.Show();
}
}
mainview.xaml
<Window x:Class="WpfViewModelFirst.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding MyProp}" Grid.ColumnSpan="2" Grid.Row="0"/>
<ContentPresenter Content="{Binding MyPivot}" Grid.Row="1" Grid.Column="0" />
<ContentPresenter Content="{Binding MyOther}" Grid.Row="1" Grid.Column="1" />
</Grid>
</Window>
mainviewmodel.cs
public class MainViewModel
{
public string MyProp { get; set; }
public PivotViewModel MyPivot { get; set; }
public OtherViewModel MyOther { get; set; }
public MainViewModel()
{
this.MyProp = "Main VM";
this.MyPivot = new PivotViewModel();
this.MyOther = new OtherViewModel();
}
}
PivotViewmodel
public class PivotViewModel
{
public string MyProp { get; set; }
public ObservableCollection<string> MyList { get; set; }
public PivotViewModel()//Dependency here with constructor injection
{
this.MyProp = "Test";
this.MyList = new ObservableCollection<string>(){"Test1", "Test2"};
}
}
OtherViewmodel
public class OtherViewModel
{
public string MyProp { get; set; }
public OtherChildViewModel MyChild { get; set; }
public OtherViewModel()
{
this.MyProp = "Other Viewmodel here";
this.MyChild = new OtherChildViewModel();
}
}
OtherChildViewmodel
public class OtherChildViewModel
{
public string MyProp { get; set; }
public OtherChildViewModel()//Dependency here with constructor injection
{
this.MyProp = "Other Child Viewmodel";
}
}
PivotView
<UserControl x:Class="WpfViewModelFirst.PivotView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding MyProp}" Grid.Row="0"/>
<ListBox ItemsSource="{Binding MyList}" Grid.Row="1"/>
</Grid>
</UserControl>
OtherView
<UserControl x:Class="WpfViewModelFirst.OtherView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding MyProp}" Grid.Row="0" />
<ContentPresenter Content="{Binding MyChild}" Grid.Row="1"/>
</Grid>
</UserControl>
OtherChildView
<UserControl x:Class="WpfViewModelFirst.OtherChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock Text="{Binding MyProp}" />
</Grid>
</UserControl>