I need to open a dialog window when I open a module.
In my module I register WindowA and I want to show it in method OnInitialize() of the module. It looks like this.
public class TestModule : IModule
{
IDialogWindow _dialogWindow;
public TestModule(IContainerProvider containerProvider, IDialogWindow dialogWindow)
{
_dialogWindow = dialogWindow;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_dialogWindow.Show();
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterDialog<WindowA>();
}
}
My window
<Window x:Class="FirstModule.Views.WindowAView"
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/"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:local="clr-namespace:FirstModule.Views"
mc:Ignorable="d"
Title="WindowA" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Content="Button"/>
</Grid>
</Window>
and View model for this
class WindowAViewModel : IDialogAware
{
public WindowAViewModel()
{
}
public string Title { get; }
public event Action<IDialogResult> RequestClose;
public bool CanCloseDialog()
{
return true;
}
public void OnDialogClosed()
{
}
public void OnDialogOpened(IDialogParameters parameters)
{
}
}
Also I implemented IDialogWindow
public partial class WindowAView : Window, IDialogWindow
{
public WindowAView()
{
InitializeComponent();
}
public IDialogResult Result { get; set; }
}
But instead of WindowAView a small window is shown with Width and Height equal to 0.
Could you please explain what I did wrong?
I think you misunderstood the dialog service, from the documentation:
Your dialog view is a simple UserControl that can be designed anyway you please. The only requirement it has a ViewModel that implements IDialogAware set as it's DataContext.
The dialog view is a UserControl that is shown in a dialog host. Prism uses a standard WPF window as default dialog host, but you can create your own by implementing the IDialogWindow interface and registering it. In Prism 7, there was only one dialog host for all. Since Prism 8, you can use the same dialog host for all dialog views or different dialog hosts as you want.
It's very common to be using a third-party control vendor such as Infragistics. In these cases, you may want to replace the standard WPF Window control that hosts the dialogs with a custom Window class such as the Infragistics XamRibbonWindow control.
In this case, just create your custom Window, and implement the IDialogWindow interface: [...] Then register your dialog window with the IContainerRegistry. [...] If you have more than one dialog window you would like to use as a dialog host, you register multiple dialog windows with the container by specifying a name for the window.
You inject the IDialogWindow into the module constructor, which is not your WindowAView, but simply the default dialog host window, which is empty, as there is no content to host. What you should do instead is define your view as a UserControl and adapt your WindowAViewModel accordingly.
<UserControl x:Class="FirstModule.Views.AView"
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/"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:local="clr-namespace:FirstModule.Views"
mc:Ignorable="d"
Height="450"
Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Content="Button"/>
</Grid>
</UserControl>
public partial class AView : UserControl
{
public AView()
{
InitializeComponent();
}
}
class AViewModel : IDialogAware
{
public AViewModel()
{
}
public string Title { get; }
public event Action<IDialogResult> RequestClose;
public bool CanCloseDialog()
{
return true;
}
public void OnDialogClosed()
{
}
public void OnDialogOpened(IDialogParameters parameters)
{
}
}
Then register the dialog and use the IDialogService instead of IDialogWindow.
public class TestModule : IModule
{
IDialogService _dialogService;
public TestModule(IContainerProvider containerProvider, IDialogService dialogService)
{
_dialogService = dialogService;
}
public void OnInitialized(IContainerProvider containerProvider)
{
dialogService.Show(nameof(AView), new DialogParameters(), result => {});
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterDialog<AView>();
}
}
This will show your view in the default dialog window, but as stated above, you can always roll your own, if you need to. For more information, see the dialog service documentation.
Related
All I have some experience with Caliburn.Micro using System.ComponentModel.Composition as an IoC container. This time I want to have some fun and use Niject. To setup the Calburn.Micro bootstrapper, I have the following class
public class Bootstrapper : BootstrapperBase
{
private IKernel _kernel;
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_kernel = new StandardKernel();
_kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
_kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
_kernel.Bind<IMainWindowViewModel>().To<MainWindowViewModel>().InSingletonScope();
}
protected override object GetInstance(Type service, string key)
{
return _kernel.Get(service);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _kernel.GetAll(service);
}
protected override void OnStartup(object sender, StartupEventArgs suea)
{
base.OnStartup(sender, suea);
DisplayRootViewFor<IMainWindowViewModel>();
}
protected override void OnExit(object sender, EventArgs e)
{
_kernel.Dispose();
base.OnExit(sender, e);
}
}
This seem to be called fine, but when the line
DisplayRootViewFor<IMainWindowViewModel>();
Is hit, it seems to launch the view IMainWindowView okay, but
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
}
}
public interface IMainWindowViewModel { }
and MainWindowViewModel as
public class MainWindowViewModel : Conductor<IMainWindowViewModel>, IMainWindowViewModel { }
with the XAML of IMainWindowView as
<Window x:Class="Mole.Replay.Framework.MainWindowView"
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:ViewModels="clr-namespace:Mole.Replay.Framework"
xmlns:local="clr-namespace:Mole.Replay.Framework"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Window.DataContext>
<ViewModels:MainWindowView/>
</Window.DataContext>
<Grid>
</Grid>
</Window>
The ctor is called over and over again and results in a StackOverflow exception, there is no clear cause for this what-so-ever. This type is bound in singleton scope. Why is this happening am I missing something?
<Window.DataContext>
<ViewModels:MainWindowView/>
</Window.DataContext>
it doesn't make sense to set DataContext of MainWindowView to another instance of MainWindowView which will also try to set DataContext etc, until you get StackOverflow exception.
it should be a view model in DataContext. I don't know if caliburn.micro creates view models for view based on conventions, but at least remove current <Window.DataContext> assignment.
it is not clear to me why view namespace is aliased as ViewModels. If real view model is in the same namespace and it is not resolved automatically, assign view model
<Window.DataContext>
<ViewModels:MainWindowViewModel/>
</Window.DataContext>
DI container really should provide constructor arguments.Use them to assign DataContext:
public MainWindowView(IMainWindowViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
I am trying to create a base class for my windows in a WPF application but I cannot access their members in the derived classes. For example, here is the base window:
namespace MyApp.Windows
{
public class BaseWindow : Window
{
public int MyProp { get; set; }
}
}
And here is a window:
public partial class SomeWindow : BaseWindow
{
public SomeWindow()
{
InitializeComponent();
Loaded += SomeWindow_Loaded;
}
private void SomeWindow_Loaded(object sender, RoutedEventArgs e)
{
MyProp = do something;
}
}
If I leave it like this, the MyProp proerty works just fine but I get an error that InitializeComponent() is not recognized. Therefore, in the window xaml I change the x:Class as follows:
Before
<Window x:Class="MyApp.SomeWindow"
After
<Window x:Class="MyApp.Windows.BaseWindow"
Now, InitializeComponent() doesn't give me anymore issues but MyProp suddenly isn't recognized. Why?
In case it helps, what I want is to have all windows raise an event once they are loaded (the Loaded event is fired) and I don't want to write this code for every window I have, so I thought I could write this code in a base class, derive my windows from this and, well, have everything work.
Edit: Here's all the code.
BaseWindow.cs (no other xaml):
using System.Windows;
namespace MyApp.Windows
{
public class BaseWindow : Window
{
public int MyProp { get; set; }
}
}
MainWindow.xaml.cs
namespace MyApp.Windows
{
public partial class MainWindow : BaseWindow
{
public MainWindow()
{
InitializeComponent();
}
}
}
MainWindow.xaml:
<myapp:BaseWindow x:Class="MyApp.Windows.BaseWindow"
xmlns:myapp="clr-namespace:MyApp.Windows"
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:MyApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</myapp:BaseWindow>
In order to change the base class of SomeWindow from Window to BaseWindow, you need to replace Window by BaseWindow wherever it occurs.
So
public partial class SomeWindow : Window
becomes
public partial class SomeWindow : BaseWindow
and
<Window x:Class="MyApp.Windows.SomeWindow" ...>
becomes
<myapp:BaseWindow x:Class="MyApp.Windows.SomeWindow"
xmlns:myapp="clr-namespace:MyApp.Windows" ...>
with the unavoidable XAML namespace prefix.
This is the BaseWindow class used in the example above:
namespace MyApp.Windows
{
public class BaseWindow : Window
{
public int MyProp { get; set; }
public BaseWindow()
{
Loaded += BaseWindow_Loaded;
}
private void BaseWindow_Loaded(object sender, RoutedEventArgs e)
{
}
}
}
I'm trying to use a TabControl to switch between UserControls.
I could just set the content of the tabs to the usercontrols with XAML but then it will only be bound to the view and not the viewmodel.
My VM is a Caliburn.Micro Conductor and it calls ActivateItem whenever the user switches tabs. It worked fine when I only have one usercontrol, but when I created another one the first one will not load the view.
Here's some of the code I'm using:
ShellView:
<dx:ThemedWindow x:Class="PSCServiceManager.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Title="Service Manager" WindowState="Maximized"
Height="525" Width="720">
<Grid>
<dx:DXTabControl>
<dx:DXTabItem Header="Master Teknisi">
<ContentControl x:Name="LoadMasterTechnicianView" cal:View.Model="{Binding ActiveItem}" />
</dx:DXTabItem>
<dx:DXTabItem Header="Servisan">
<ContentControl x:Name="LoadServicesView" cal:View.Model="{Binding ActiveItem}" />
</dx:DXTabItem>
</dx:DXTabControl>
</Grid>
ShellViewModel:
using Caliburn.Micro;
namespace PSCServiceManager.ViewModels
{
public class ShellViewModel : Conductor<IScreen>
{
private MasterTechnicianViewModel masterTechnicianViewModel;
private ServicesViewModel servicesViewModel;
public ShellViewModel()
{
LoadMasterTechnicianView();
}
public void LoadMasterTechnicianView()
{
ActivateItem(masterTechnicianViewModel);
}
public void LoadServicesView()
{
ActivateItem(servicesViewModel);
}
}
}
An easier/alternative way to implement this would be to create a collection of User Controls you would like to bind to the Tab Control. For example,
public interface ITabUserControl
{
string DisplayName { get; set; }
}
public class MasterTechnicianViewModel : ITabUserControl
{
public string DisplayName { get; set; } = "Master Technician";
}
public class ServicesViewModel : ITabUserControl
{
public string DisplayName { get; set; } = "Services";
}
Now in your ShellViewModel, you could create a Collection of ITabUserControl
public List<ITabUserControl> UserControls { get; set; }
public ShellViewModel()
{
UserControls = new List<ITabUserControl>();
UserControls.Add(new MasterTechnicianViewModel());
UserControls.Add(new ServicesViewModel());
}
And bind your TabControl as
<dx:DXTabControl x:Name="UserControls"/>
Now you can switch between the controls without any issues, without Activating it explicitly.
I have a small example github repo in which I like to open a custom ContentDialog (SpeechDialog) after a button is clicked, using the MVVMCross framework.
If I implement a ContentDialog with MVVM without a framework, the MainView will look like this:
public sealed partial class MainView : Page
{
public MainView()
{
this.InitializeComponent();
ISpeechDialogService dialog = new SpeechDialogService();
MainViewModel= new MainViewModel(dialog);
}
public MainViewModel MainViewModel{ get; set; }
}
But in MVVMCross I have an attributed MainView and I don't know how to pass the ContentDialog:
[MvxViewFor(typeof(MainViewModel))]
public sealed partial class MainView : MvxWindowsPage
{
public MainView()
{
InitializeComponent();
}
}
Some code for a better understanding:
SpeechDialogService.cs:
public class SpeechDialogService : ISpeechDialogService
{
public async Task ShowAsync()
{
var contentDialog = new Speech();
await contentDialog.ShowAsync();
}
}
directlink to the Speech.xaml
TL;DR
Is my approach right? If yes, how can I pass a ContentDialog to the MainViewModel? If not, how to implement a ContentDialog with MVVMCross?
I think you can use ViewModelLocator here and use MVVM pattern, regardless of the framework. See sample implementation.
public class ViewModelLocator
{
public MainPageViewModel MainPageViewModel
{
get
{
ISpeechDialogService dialogService = new SpeechDialogService();
MainPageViewModel vm = new MainPageViewModel(dialogService);
return vm;
}
}
}
Here, you can use Autofac to resolve your ViewModels' dependecies, also make the service singleton https://autofaccn.readthedocs.io/en/latest/resolve/index.html
Then in your App.xaml, add a resource for locator:
<Application.Resources>
<services:ViewModelLocator x:Key="Locator" />
</Application.Resources>
Then in your page (preferably not in code behind) you should assign your DataContext like so:
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
d:DesignHeight="450" d:DesignWidth="800"
DataContext="{Binding MainPageViewModel, Source={StaticResource Locator}}">
</Page>
Then your ViewModel should look like:
using App1.Services;
namespace App1.ViewModels
{
public class MainPageViewModel
{
private readonly ISpeechDialogService _speechDialogService;
public MainPageViewModel(ISpeechDialogService speechDialogService)
{
_speechDialogService = speechDialogService;
}
}
}
Your dialog service look like:
using System.Threading.Tasks;
namespace App1.Services
{
public class SpeechDialogService : ISpeechDialogService
{
public async Task ShowAsync()
{
var contentDialog = new Speech();
await contentDialog.ShowAsync();
}
}
}
Hope this helps.
I can't get the navigation in Prism to work. When I click on the buttons to go to respective views, nothing happens.
This is the Man View (Shell) XAML:
<Window x:Class="MVVMPractice2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:Views="clr-namespace:MVVMPractice2.Views"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Margin="108,130,331.4,152.8" Content="View A" Command="{Binding NavigateCommand}" CommandParameter="ViewA"/>
<Button Margin="254,130,185.4,152.8" Content="View B" Command="{Binding NavigateCommand}" CommandParameter="ViewB"/>
<ContentControl prism:RegionManager.RegionName="ContentRegion"/> <!--PRISM POWER-->
</Grid>
</Window>
and its ViewModel:
public class MainWindowViewModel : BindableBase
{
private readonly IRegionManager regionManager; //PRISM POWER
public DelegateCommand<string> NavigateCommand { get; set; }
public MainWindowViewModel(IRegionManager regionManager)
{
this.regionManager = regionManager;
NavigateCommand = new DelegateCommand<string>(Navigate);
}
private void Navigate(string uri)
{
regionManager.RequestNavigate("ContentRegion", uri);
}
}
and Bootstrapper:
public class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType(typeof(object), typeof(ViewA), "ViewA");
Container.RegisterType(typeof(object), typeof(ViewB), "ViewB");
Container.RegisterType<ICustomer, Customer>();
}
}
I would appreciate some help.
I got mine to work by using the prism:ViewModelLocator.AutoWireViewModel="True" on UserControls only and not the window. I am assuming that you are using prism 6.
So what I did is, I first created the MainWindow that is going to house all my UserControls, I then created a MainUserControl that would house all the other UserControls. All this I achieved following this blog post (http://brianlagunas.com/getting-started-prisms-new-viewmodellocator/). Remember to create your MVVM folders (View and ViewModel) folders with their respective contents as the blog highlights.
Hope this helps.
First of all you should expose ICommand to button's command property , not delegate command which is concrete implementation of ICommand.
You can get rid of conventions of view model locator by implementing
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) in application class startup overriden method.
For more info please search Brian Lagunas viewmodellocator blog