Caliburn Micro self-replacing View/ViewModel - wpf

I have a listbox to select an item for edit. I have an edit button as well. Call this the MainView[Model].
If I press the edit button the MainView[Model] shall be replaced by EditView[Model].
The EditView shall not be displayed in a area below or beside the MainView. It should be completely replaced or at least completely hide the MainView.
If edit is finished (OK, cancel) the MainView shall be displayed again.
I have tried to overlay a ContentControl but with no success.
Now, I'm thinking about a kind of NavigatorViewModel which has multiple ViewModels exposed by a property. But I'm not sure if this is the right direction to go.
Can anybody help?
Thx.

You would preferably use the Conductor pattern that Caliburn.Micro provides. A conductor manages one or more Screens and controls their lifetime. See Screens, Conductors and Composition for further information.
At first, we need a shell. This is your "NavigatorViewModel". It is derived from Conductor<Screen>.Collection.OneActive, what means that it holds a list of Screens of which a single one can be active at a time:
public interface IShell
{
void ActivateItem(Screen screen);
}
public class ShellViewModel : Conductor<Screen>.Collection.OneActive, IShell
{
public ShellViewModel()
{
this.ActivateItem(new MainViewModel());
}
}
A conductor has an ActiveItem property, and we want to bind a ContentControl to it, so we see the corresponding view:
<!-- ShellView.xaml -->
<Window x:Class="WpfApplication1.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ContentControl Name="ActiveItem" />
</Window>
Our MainViewModel can navigate to the EditViewModel using its parent, the shell:
public class MainViewModel : Screen
{
public void Edit()
{
((IShell)this.Parent).ActivateItem(new EditViewModel());
}
}
We bind a button to the Edit method:
<!-- MainView.xaml -->
<UserControl x:Class="WpfApplication1.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Button Name="Edit" Content="Edit" />
</UserControl>
EditViewModel also derives from Screen and just contains your edit logic:
public class EditViewModel : Screen
{
}
Finally, we bind a button to the TryClose method, so the view model closes itself and is removed from the shell's items. The last activated item (MainViewModel) will be reactivated:
<!-- EditView.xaml -->
<UserControl x:Class="WpfApplication1.EditView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Button Name="TryClose" Content="Back" />
</UserControl>
That's about it.

Related

WPF Caliburn Micro: Exchanging UserControls in a Window dynamically using ContentControl

This question is related to Add a usercontrol to caliburm micro dynamically.
I have read any other related threads before open this new thread, but I still don't understand and find no solution. Please accept my apology if some of you take this as duplicate.
I have a window (MainView) contains "main" Grid (aka LayoutRoot) with 2 columns.
On left column there are 2 buttons: "Display View 1" and "Display View 2".
If user click "Display View 1", the "Display1View" (is a UserControl contains TextBlock with Text "View 1") should be shown on the right column, replace the current one.
If user click "Display View 2", the "Display2View" (is a UserControl contains TextBlock with Text "View 2") should be shown on the right column, replace the current one.
My sample code contains following views and viewmodels:
MainView.xaml and MainViewModel.cs
Display1View.xaml and Display1ViewModel.cs
Display2View.xaml and Display2ViewModel.cs
In my sample code the ContentControl doesn't recognize the UserControl. What am I doing wrong? How to bind ContentControl correctly? Please feel free to modify my sample code. Thank you in advance
MainView.xaml
<Window x:Class="TestCaliMiContentControl.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Main View"
Width="525"
Height="350">
<Grid x:Name="LayoutRoot" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30*" />
<ColumnDefinition Width="100*" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="LeftNavPanel" Grid.Column="0">
<Button x:Name="Display1" Content="Display View 1" />
<Button x:Name="Display2" Content="Display View 2" />
</StackPanel>
<ContentControl x:Name="MainGridContent" Grid.Column="1" />
</Grid>
</Window>
MainViewModel.cs
public class MainViewModel : PropertyChangedBase
{
private ContentControl _mainGridContent;
public ContentControl MainGridContent
{
get { return _mainGridContent; }
set
{
_mainGridContent = value;
NotifyOfPropertyChange(() => MainGridContent);
}
}
public void Display1()
{
//MainGridContent = new Display1ViewModel(); // cannot convert source type error
}
public void Display2()
{
// MainGridContent = new Display2ViewModel(); // cannot convert source type error
}
}
Display1View.xaml
<UserControl x:Class="TestCaliMiContentControl.Display1View"
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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<TextBlock HorizontalAlignment="Center" FontSize="72"
Text="View 1"/>
</Grid>
</UserControl>
Display1ViewModel.cs
using System;
using System.Windows.Controls;
using Caliburn.Micro;
namespace TestCaliMiContentControl
{
public class Display1ViewModel : PropertyChangedBase {}
}
First, I would start by recommending you read the Caliburn.Micro documentation, specifically the part about Screens, Conductors, and Composition: http://caliburnmicro.com/documentation/composition
That being said, we can modify your code to get it working.
1) Since your MainViewModel is supposed to be conducting other items, it should descend from Conductor<T>. In this case, we will have it conduct the Caliburn Screen class.
public class MainViewModel : Conductor<Screen>
2) In MVVM, you view models should know nothing of your view. You should not see UI classes such as ContentControl. We could change your property to be of type Screen, but we actually don't need that property at all since we are using a conductor. So, remove the MainGridContent property and backing field.
3) Within your Display1 and Display2 methods, invoke Caliburn's conductor method ActivateItem to show the appropriate item.
public void Display1()
{
ActivateItem(new Display1ViewModel());
}
4) In your MainView.xaml you will need to bind your ContentControl to the conductor's active item property, which is, by convention, ActiveItem.
<ContentControl x:Name="ActiveItem" Grid.Column="1" />
5) Finally, since your conductor is conducting Screens, you need to make them screens. Screens are helpful because they have lifecycle and allow you to know when they are activated/deactivated. Do this for both Display1 and Display2.
public class Display1ViewModel : Screen {}
This should get you up and running.

Handle UIElements with MVVM

Im building an app trying to follow MVVM as far as possible. Its an windows 8 store app and Im using MVVM-light. Now let say I have a MainPage using MainViewModel and I also have a UserControl using UserViewModel.
Now I want to be able to communicate between MainPage and UserControl without using any code-behind. In this simple scenario i would like to "click" a button in my UserControl from a button in MainPage
MainPage.Xaml:
Here I have a button with which I wish to click a button in the Usercontroll
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="MyButton"
Command="MyCommand"></Button>
</Grid>
MainPageViewModel:
public class MainViewModel : ViewModelBase
{
//
}
UserControll.Xaml
Here is the button I wish to click from MainPage
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="UcButton"/>
</Grid>
UserControlViewModel:
public class UserControlViewModel : ViewModelBase
{
//
}
Im interested in knowing about how you can interact with UI-Elements following the MVVM-pattern. Any links or tips appreciated.
The 'clean' (MVVM way) of doing this is by having the ViewModels interact.
So if View1 should invoke something in View2 have View1 trigger a Command on its ViewModel1. ViewModel1 should then contact ViewModel2. ViewModel2 would executed the required action and show the result through its properties. View2 will bind to the properties and show the effect of the action.
Interaction between ViewModels can be done through a publisher-subscriber model (message broker, bus, ...) In MVVM-light there is a Messenger class to keep the dependencies clean.
I can't comment since I don't have enough points, and the question seems a bit weird. Do you want to click the button so that it shows in the GUI, like the button turns blue or do you want to invoke a command in usercontrolViewModel. If it is the latter you could maybe do a fix like this:
MainPage.xaml
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Content="MyButton"
Command="{Binding UserControlViewModel.SomeUserControlCommand, Source={StaticResource Locator}}"></Button>
</Grid>
Remember to check what name you gave UserControlViewModel in the ViewModelLocator and you will of course have to create the command: SomeUserControlCommand in UserControlViewModel.

Keeping page code in separate files

I have a main WPF Window with a Menu Bar and a Status Bar. I want this window to act as MDI Window. I want to define the entire area from below the Menu Bar to just above the Status Bar, where I will be showing other Windows. I want to define a Page or Panel like thing. When I add a new WPF Form, I want it to appear on this location contained within the Main Window. How to do this?
I've used http://wpfmdi.codeplex.com/ in basic prototype apps. Create your content as user controls, which are then wrapped in a window.
I've never used it with the toolbox, though.
Simple version:
<Window x:Class="WpfApplication1.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">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="File"/>
<MenuItem Header="Edit"/>
<MenuItem Header="Help"/>
</Menu>
<StatusBar DockPanel.Dock="Bottom"/>
<ContentControl Name="_content"/>
</DockPanel>
</Window>
And code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public object MainContentGoesHere
{
get { return _content.Content; }
set { _content.Content = value; }
}
}
Then in your code just have a reference to a MainWindow - and use the MainContentGoesHere property to inject the views. Just remember to use UserControls, Panels or ContentControl instead of Window, when you inject them as Window cannot have a parent.
If you want something more fancy - take a look at Prism on CodePlex. It might be overkill for you, but it is actually a well-thought out and lightweight framework.
EDIT: Fixed the order of Children in the DockPanel.

MVVM WPF design related query : use of UserControls

I have one query related to designing WPF using MVVM
Here is the scenario :
1> I have one WPF screen which contains various user controls which are reusable in some other screens too.
2> Can i have separate ViewModel class for each of those user controls , what could be ideal design in this scenario
3> Should i separate my Viewmodel based on individual screen or on UserControls .
4> If i create separate viewmodels based on UserControls how i should integrate it .
Is there any design guidelines around this !!
Urgent Help appreciated ..
This post describes what I do in certain scenario, I don't know if it is a best practice or not but it works for me.
I create ViewModel for my Window that holds all the user controls, so this called ContainerViewModel and I create an instance of that Viewmodel and put it in the DataContext of the Window. From that moment all the UserControls can access that ViewModel with Binding.
The next thing to do is to create a property on my ContainerViewModel for everty UserControl that holds the ViewModel for each UserControl.
Then use binding to attach the usercontrols ViewModel to the DataContext property of the Usercontrol.
example of the viewmodels and a window with 2 listboxes instead of usercontrols:
Viewmodel classes without any implementation but just empty classes to show the concept:
public class ContainerViewModel
{
public ContainerViewModel()
{
ViewModelForControl1 = new Control1ViewModel();
ViewModelForControl2 = new Control2ViewModel();
}
public Control1ViewModel ViewModelForControl1 { get; set; }
public Control2ViewModel ViewModelForControl2 { get; set; }
}
public class Control1ViewModel { }
public class Control2ViewModel { }
Window xaml:
<Window x:Class="ConfigHellp.UI.Windows.ContainerWindow"
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:vm="clr-namespace:ConfigHellp.UI.ViewModel"
mc:Ignorable="d"
DataContext="{DynamicResource ContainerViewModel}" >
<Window.Resources>
<vm:ContainerViewModel x:Key="ContainerViewModel" d:IsDataSource="True" />
</Window.Resources>
<StackPanel>
<ListBox DataContext="{Binding ViewModelForControl1}" />
<ListBox DataContext="{Binding ViewModelForControl2}" />
</StackPanel>
</Window>
this depends on how complex the embedding of the UserControl into the environment is. If you think that its to much effort to build the view model logic for your user control again and again (which is also a very nice source for mistakes), you should infact encapsulate the logic in a single viewmodel for your control. If the user control will be an ListItem for example, i generally suggest you to build an own viewmodel for the control.
The infrastructure will be than:
A general viewmodel for your WPF screen, which holds instances of the viewmodels for your usercontrols. That DataContext of the screen will be the general viewmodel. The users controls's DataContext will be a Binding to the PropertyPath of the user control viewmodel in your general viewmodel. e.g:
In WPF Screen:
<ListBox DataContext="{Binding}" ItemsSource="{Binding Path=ItemList}">
<ListBox.ItemTemplate>
<yourControls:YourUserControl />
</ListBox.ItemTemplate>
</ListBox>
In the general viewmodel:
public class ScreenViewModel : INotifyPropertyChanged
{
private ObservableCollection<YourUserControlViewModel> _itemList =
new ObservableCollection<YourUserControlViewModel>();
public ObservableCollection<YourUserControlViewModel> ItemList
{
get { return _itemList; }
set { _itemList = value; }
}
}
This will automatically generate a your user control for each viewmodel in the ItemList of your general view model.

Create a custom click event handler for a WPF usercontrol which contains a button?

have you ever found a problem when assigning a click event handler for your custom WPF usercontrol with a nested button control? I do.
When you put such user control in a main window, let's say Main.xaml, the MouseLeftButtonDown doesn't work, but the PreviewMouseLeftButtonDown works like a charm.
But imagine yourself telling each developer in your team to use this event when using your usercontrol... Some usercontrols in you library has MouseLeftButtonDown, others PreviewMouseLeftButtonDown.... It's a mess don't you agree?
So I've got a solution but I want someone to see if there's some elegant way to create your custom event handler called "Click".
In my usercontrol called CustomButton.xaml.cs, I have so far:
public partial class CustomButton: UserControl
{
public CustomButton()
: base()
{
this.InitializeComponent();
}
public delegate void ClickHandler(object sender, EventArgs e);
public event EventHandler Click;
public void Button_Click(object sender, RoutedEventArgs e) {//execute daddy's button click
(((sender as Button).Parent as Grid).Parent as CustomButton).Click(sender, e);
e.Handled = false;
}
In my CustomButton.xaml
<UserControl
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"
mc:Ignorable="d"
x:Class="YourCompany.UI.Controls.CustomButton" d:DesignHeight="72.5" d:DesignWidth="200">
<UserControl.Resources>
blablabla
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Button Style="{DynamicResource CustomButton}"
Width="{Binding ElementName=CustomButton, Path=ActualWidth}"
Cursor="Hand" Foreground="#ffffff" FontSize="28" Margin="8,8,0,12"
HorizontalAlignment="Left"
Content="Custom Button" Click="Button_Click" />
</Grid>
Now in my Main.xaml, the caller, I have:
<Window x:Class="YourCompany.MyProject.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyProject!" Height="600" Width="800"
MinWidth="800" MinHeight="600" WindowState="Maximized" WindowStartupLocation="CenterScreen"
xmlns:bigbola="clr-namespace:YourCompany.UI.Controls;assembly=YourCompany.UI.Controls">
<mycontrols:CustomButton Name="test" MyImage=".\Images\btnOptions.png" Cursor="Hand"
Texto="See options" Click="test_Click"
Margin="168.367,176.702,253.609,0" ToolTip="See all options" Height="76.682"
VerticalAlignment="Top"></mycontrols:CustomButton>
Explanation:
in the usercontrol, when you click the nested button, it executes its parent custom "Click" handler.
Is there a elegant way to accomplish the same effect?
Going off of what mdm20 was saying... Why are you creating a UserControl (a collection of controls grouped into 1) when you could much more easily create a CustomControl (a control that extends the functionality of an existing control, such as a Button)? Assuming a Button is the only control you'd like in CustomButton, I'd highly recommend a CustomControl over what you have (a UserControl).
Example of UserControl vs CustomControl here
Hope this helps!
If your implementing a button, why not just derive from button?
To answer your question though, all you need it this.
if (Click != null) Click(this, EventArgs.Empty);
Couldn't this line:
(((sender as Button).Parent as Grid).Parent as CustomButton).Click(sender, e);
be replaced by
this.Click(sender, e);
?
Other than that though the answer depends on the exact behaviour that you want. If you want to click event of your user control to only trigger when you click on the inner button then I think you are handling it the right way. On the other hand if you want the click event to trigger whenever you click anywhere within the bounds of the user control then you are probably best styling or inheriting from the standard button control. Remember that in WPF the button's content can be any other element including another button.

Resources